الگوی طراحی Builder یک الگوی طراحی خلاقانه است که به ما این امکان را می دهد تا یک آبجکت را در یک مرحله و یکبار ایجاد کنیم. استفاده از این الگو هنگام ایجاد یک آبجکت پیچیده کاملاً متداول است. با استفاده از این الگو، می توانیم قسمت های مختلف یک آبجکت را مرحله به مرحله ایجاد کنیم و سپس همه قسمت ها را به هم متصل کنیم.
بدون این الگو، ما با یک سازنده بزرگ که تمام پارامترهای مورد نیاز را برای ایجاد آبجکت ما دربرمیگیرد مواجه میشویم. این می تواند منجر به ایجاد کدی ناخوانا با قابلیت نگهداری سخت شود. علاوه بر این، یک سازنده با مقدار زیادی پارامتر، جنبه منفی دارد. ما نیازی نداریم تا همیشه از تمام پارامترها استفاده کنیم.
این مقاله، بخشی از مجموعه آموزشی زیر است:
- الگوی طراحی Builder و Fluent Builder
- اینترفیس Fluent Builder به همراه Generic بازگشتی
- Facated Builder
- متد Factory
- Singleton
- Adapter
- Composite
- Decorator
- Command
- Strategy
- Facade
در این مقاله، ما به شما نشان میدهیم که چطور برای اجتناب از چنین سازنده های پیچیده ای، الگوی Builder را پیاده سازی کنید. ما حتی از این فراتر خواهیم رفت و الگوی طراحی Builder Recursive Generics و Faceted Builder را همچنین در مقالات بعدی توضیح می دهیم.
شما میتوانید سورس را از اینجا دانلود کنید: الگوی طراحی Builder – سورس کد
برای مشاهده صفحه اصلی این سری از آموزش، الگوهای طراحی #C را بررسی کنید.
این مقاله به قسمتهای زیر تقسیم میشود:
پیاده سازی الگوی طراحی Builder
ما قصد داریم یک مثال ساده از ایجاد گزارش موجودی برای تمام محصولات فروشگاه خود بنویسیم.
پس، با یک کلاس ساده Product، شروع میکنیم:
1 2 3 4 5 |
public class Product { public string Name { get; set; } public double Price { get; set; } } |
ما از این کلاس فقط برای ذخیره مقداری data پایه برای یک محصول واحد استفاده میکنیم.
آبجکت گزارش موجودی ما قرار است که شامل قسمتهای header,body و footer باشد. بنابراین، کاملا منطقی است که فرایند ساخت آبجکت را به این سه عمل تقسیم کنیم. اول با کلاس ProductStockReport شروع میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class ProductStockReport { public string HeaderPart { get; set; } public string BodyPart { get; set; } public string FooterPart { get; set; } public override string ToString() => new StringBuilder() .AppendLine(HeaderPart) .AppendLine(BodyPart) .AppendLine(FooterPart) .ToString(); } |
این آبجکتی است که ما قصد داریم آن را با استفاده از الگوی طراحی Builder بسازیم.
برای ادامه، ما به یک اینترفیس builder برای سازماندهی فرآیند ساخت نیاز داریم:
1 2 3 4 5 6 7 |
public interface IProductStockReportBuilder { void BuildHeader(); void BuildBody(); void BuildFooter(); ProductStockReport GetReport(); } |
ما میببینیم که کلاس سازنده واقعی که قرار است این اینترفیس را پیاده سازی کند، باید تمام قسمت ها را برای آبجکت گزارش موجودی ما ایجاد کرده و آن آبجکت را نیز برگرداند. پس کلاس builder واقعی را پیاده سازی میکنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public class ProductStockReportBuilder : IProductStockReportBuilder { private ProductStockReport _productStockReport; private IEnumerable<Product> _products; public ProductStockReportBuilder(IEnumerable<Product> products) { _products = products; _productStockReport = new ProductStockReport(); } public void BuildHeader() { _productStockReport.HeaderPart = $"STOCK REPORT FOR ALL THE PRODUCTS ON DATE: {DateTime.Now}\n"; } public void BuildBody() { _productStockReport.BodyPart = string.Join(Environment.NewLine, _products.Select(p => $"Product name: {p.Name}, product price: {p.Price}")); } public void BuildFooter() { _productStockReport.FooterPart = "\nReport provided by the IT_PRODUCTS company."; } public ProductStockReport GetReport() { var productStockReport = _productStockReport; Clear(); return productStockReport; } private void Clear() => _productStockReport = new ProductStockReport(); } |
این logic کاملا واضح و روشن است. اینجا ما تمام محصولات مورد نیاز برای گزارشمان را دریافت میکنیم و آبجکت _productStockReport
را نمونه سازی میکنیم. سپس تمام قسمتهای آبجکتمان را میسازیم و در آخر آن را برمیگردانیم. در متد GetReport
، آبجکت خود را ریست میکنیم و یک نمونه جدید را جهت آماده سازی برای ایجاد گزارش دیگر آماده میکنیم. این یک رفتار معمول است اما الزامی در آن نیست.
زمانیکه منطق ساخت ما به پایان رسید، میتوانیم ساخت آبجکتمان را در یک کلاس client شروع کنیم یا حتی داخل یک کلاس Director، فرآیند ساخت را از کلاس client کپسوله کنیم. خب، این دقیقا کاری است که ما قرار است انجام دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class ProductStockReportDirector { private readonly IProductStockReportBuilder _productStockReportBuilder; public ProductStockReportDirector(IProductStockReportBuilder productStockReportBuilder) { _productStockReportBuilder = productStockReportBuilder; } public void BuildStockReport() { _productStockReportBuilder.BuildHeader(); _productStockReportBuilder.BuildBody(); _productStockReportBuilder.BuildFooter(); } } |
ایجاد آبجکت StockReport
بعد از آنکه تمام کارها را انجام دادیم, میتوانیم ساخت آبجکتمان را شروع کنیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Program { static void Main(string[] args) { var products = new List<Product> { new Product { Name = "Monitor", Price = 200.50 }, new Product { Name = "Mouse", Price = 20.41 }, new Product { Name = "Keyboard", Price = 30.15} }; var builder = new ProductStockReportBuilder(products); var director = new ProductStockReportDirector(builder); director.BuildStockReport(); var report = builder.GetReport(); Console.WriteLine(report); } } |
نتیجه:
عالی. ما آبجکتمان را با استفاده از الگوی طراحی Builder ایجاد کردیم.
Fluent Builder
Fluent builder یک تغییری کوچک از الگوی طراحی Builder است که به ما این اجازه را میدهد تا فراخوانی های Builder خود را جهت اقدامات مختلف، به یکدیگر زنجیر کنیم. برای پیاده سازی Fluent builder، باید اول اینترفیس builder را تغییر دهیم:
1 2 3 4 5 6 7 |
public interface IProductStockReportBuilder { IProductStockReportBuilder BuildHeader(); IProductStockReportBuilder BuildBody(); IProductStockReportBuilder BuildFooter(); ProductStockReport GetReport(); } |
باید پیاده سازی کلاس ProductStockReportBuilder
را نیز تغییر دهیم:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public IProductStockReportBuilder BuildHeader() { _productStockReport.HeaderPart = $"STOCK REPORT FOR ALL THE PRODUCTS ON DATE: {DateTime.Now}\n"; return this; } public IProductStockReportBuilder BuildBody() { _productStockReport.BodyPart = string.Join(Environment.NewLine, _products.Select(p => $"Product name: {p.Name}, product price: {p.Price}")); return this; } public IProductStockReportBuilder BuildFooter() { _productStockReport.FooterPart = "\nReport provided by the IT_PRODUCTS company."; return this; } |
در نتیجه این تغییرات، می توانیم فراخوانی ها را در کلاس Director زنجیر کنیم:
1 2 3 4 5 6 7 |
public void BuildStockReport() { _productStockReportBuilder .BuildHeader() .BuildBody() .BuildFooter(); } |
حالا اگر اپلیکیشن را اجرا کنیم، همان نتیجه را دریافت میکنیم، با این تفاوت که اینبار از اینترفیس fluent استفاده کردیم.
نتیجه گیری
در این مقاله، ما نحوه ایجاد الگوی طراحی Builder و پیاده سازی آن در پروژه برای ایجاد آبجکتهای پیچیده را یاد گرفتیم. علاوه بر این، مثالمان را برای استفاده از اینترفیس Fluent گسترش دادیم که این اجازه را به ما میدهد تا فراخوانی های Builder خود را با یکدیگر زنجیر کنیم.
در مقاله بعدی، ما نحوه پیاده سازی اینترفیس Fluent builder به همراه Recursive Generics را یاد میگیریم.