زمان تخمینی مطالعه: 14 دقیقه
الگوی Builder یک الگوی طراحی Creational است که به شما امکان میدهد اشیاء پیچیده را مرحله به مرحله بسازید. این الگو به شما اجازه میدهد تا انواع و نمایشهای مختلف یک شی را با استفاده از کد ساخت یکسان تولید کنید.
بیان مسئله: یک شی پیچیده را تصور کنید که نیاز به مقداردهی اولیه پر زحمت و گام به گام بسیاری از فیلدها و اشیاء تو در تو دارد. این چنین کد اولیهای معمولاً در داخل یک سازنده بزرگ با پارامترهای زیادی مدفون یا حتی بدتر در سراسر کد مشتری پراکنده میگردد.
به عنوان مثال، بیایید به نحوه ایجاد یک شی House فکر کنیم. برای ساختن یک خانه ساده، باید چهار دیوار و یک طبقه بسازید، یک در نصب کنید، یک جفت پنجره را بسازید و یک سقف بسازید. اما اگر خانهای بزرگتر و روشنتر، با حیاط خلوت و وسایل دیگر (مانند سیستم گرمایش، لولهکشی، و سیمکشی برق) میخواهید چه؟
ساده ترین راه حل گسترش کلاس House پایه و ایجاد مجموعهای از زیر کلاسها برای پوشش تمام ترکیبات پارامترها است. اما در نهایت شما با تعداد قابل توجهی از زیر کلاسها مواجه خواهید شد. هر پارامتر جدید، مانند سبک و مدل ایوان ساختمان، به رشد بیشتر این سلسله مراتب نیاز دارد.
رویکرد دیگری وجود دارد که شامل پرورش زیر کلاسها نمیشود. شما میتوانید یک سازنده غول پیکر درست در کلاس پایه House با تمام پارامترهای ممکن که شی خانه را کنترل میکند ایجاد کنید. در حالی که این رویکرد در واقع نیاز به زیر کلاسها را از بین میبرد، ولی مشکل دیگری هم ایجاد میکند.
در اغلب موارد، بیشتر پارامترها استفاده نمیشوند و فراخوانی سازنده را بسیار زشت میکند. به عنوان مثال، تنها کسری از خانهها دارای استخر شنا هستند، بنابراین پارامترهای مربوط به استخرهای شنا از هر ده بار 9 بار بیفایده خواهند بود.
الگوی Builder پیشنهاد میکند که کد سازنده شی را از کلاس خودش استخراج کنید و آن را به اشیاء جداگانهای به نام builder ها منتقل کنید.
الگوی ساخت شی را در مجموعهای از مراحل (buildWalls، buildDoor و غیره) سازماندهی میکند. برای ایجاد یک شی، یک سری از این مراحل را روی یک شی builder اجرا میکنید. بخش مهم کار این است که شما نیازی به فراخوانی همه مراحل ندارید. شما فقط میتوانید مراحلی را فراخوانی کنید که برای ایجاد پیکربندی خاصی از یک شی ضروری هستند. برخی از مراحل ساخت و ساز ممکن است نیاز به اجرای متفاوتی داشته باشند، زمانی که شما نیاز به ساخت نمایشهای مختلف از محصول دارید. به عنوان مثال، دیوارهای یک کابین ممکن است از چوب ساخته شوند، اما دیوارهای قلعه باید با سنگ ساخته شوند.
در این حالت، میتوانید چندین کلاس builder مختلف ایجاد کنید که مجموعه مراحل ساختمانی یکسانی را به شیوهای متفاوت اجرا میکنند. سپس میتوانید از این builder ها در فرآیند ساخت (یعنی مجموعهای از فراخوانهای مرتب شده به مراحل ساختمان) برای تولید انواع مختلف اشیا استفاده کنید.
برای مثال، سازندهای(builder) را تصور کنید که همه چیز را از چوب و شیشه میسازد، سازنده دومی هم وجود دارد که همه چیز را با سنگ و آهن میسازد و همچنین سازنده سومی هم هست که از طلا و الماس استفاده میکند. با فراخوانی همان مجموعه گامها، از سازنده اول یک خانه معمولی، از سازنده دوم یک قلعه کوچک و از سومی یک قصر دریافت میکنید. با این حال، این تنها در صورتی کار میکند که کد کلاینت که مراحل ساختمان را فراخوانی میکند، بتواند با استفاده از یک اینترفیس مشترک با سازندهها تعامل داشته باشد.
کلاس Director
برای استفاده از الگوی Builder میتوانید گام را فراتر گذاشته و یک سری فراخوان به مراحل سازندهای که برای ساختن یک محصول استفاده میکنید در یک کلاس جداگانه به نام Director استخراج کنید. کلاس Director ترتیب اجرای مراحل ساختمان را مشخص میکند، در حالی که سازنده اجرای آن مراحل را ارائه میدهد.
داشتن یک کلاس Director در برنامه مورد استفاده شما به شدت ضروری نیست. همیشه میتوانید مراحل ساخت را به ترتیبی خاص مستقیماً از کد مشتری فراخوانی کنید. با این حال، کلاس Director ممکن است مکان خوبی برای قرار دادن روتینهای مختلف ساخت و ساز باشد تا بتوانید از آنها در سراسر برنامه خود استفاده مجدد کنید. علاوه بر این، کلاس Director جزئیات ساخت محصول را به طور کامل از کد مشتری پنهان میکند. مشتری فقط باید یک سازنده را با یک Director مرتبط کند، ساخت و ساز را با مدیر(Director) راه اندازی کند و نتیجه را از سازنده بگیرد.
برای تشریح بیشتر و روشن شدن موضوع بر اساس اصول مهندسی نرم افزار ساختار این الگوی طراحی را به صورت UML در تصویر زیر نشان دادهایم همچنین توضیحات هر گام به طور مشخص در زیر این تصویر نوشته شده است.
- گام 1: اینترفیس Builder مراحل ساخت محصول را که برای همه انواع سازندهها مشترک است، اعلام(Declares) میکند.
- گام 2: سازندههای Concrete پیادهسازیهای مختلفی از مراحل ساخت و ساز را ارائه میدهد. سازندههای Concrete ممکن است محصولاتی تولید کنند که از اینترفیس مشترک پیروی نمیکنند.
- گام 3: در این مرحله محصولات تولید شده، اشیاء حاصل هستند. محصولات ساخته شده توسط سازندههای مختلف لازم نیست به یک سلسله مراتب کلاس یا اینترفیس تعلق داشته باشند.
- گام 4: کلاس Director ترتیب فراخوانی مراحل ساخت و ساز را مشخص میکند، بنابراین میتوانید پیکربندیهای خاصی از محصولات را ایجاد و دوباره استفاده کنید.
- گام 5: مشتری باید یکی از اشیاء سازنده را با Director مرتبط کند. معمولاً این کار فقط یک بار از طریق پارامترهای سازنده Director انجام میشود. سپس Director از آن شی سازنده برای ساخت و سازهای بعدی استفاده میکند. با این حال، یک رویکرد جایگزین برای زمانی که مشتری شی سازنده را به روش تولید Director منتقل میکند، وجود دارد. در این صورت، میتوانید هر بار که با Director چیزی تولید میکنید، از سازنده متفاوتی استفاده کنید.
شبه کد(Pseudocode)
این مثال از الگوی Builder نشان میدهد که چگونه میتوانید از کد ساختوساز شی مشابه در هنگام ساخت انواع مختلف محصولات، مانند اتومبیل، استفاده مجدد کنید و کتابچه راهنمای مربوطه را برای آنها ایجاد کنید.
ماشین یک شی پیچیده است که میتوان آن را به صدها روش مختلف ساخت. به جای اینکه کلاس Car را با یک سازنده بزرگ پر کنیم، کد مونتاژ خودرو را در یک کلاس خودروساز جداگانه استخراج کرده و جای دادیم. این کلاس مجموعهای از روشها برای پیکربندی قسمتهای مختلف خودرو را دارد. اگر کد مشتری نیاز به مونتاژ یک مدل خاص و دقیق از خودرو داشته باشد، میتواند مستقیماً با سازنده کار کند. از سوی دیگر، مشتری میتواند مونتاژ را به کلاس Director واگذار کند، که میداند چگونه از سازنده برای ساخت چندین مدل از محبوبترین خودروها استفاده کند.
هر ماشینی به یک دفترچه راهنما نیاز دارد (فکر نکنم کسی تا بحال خونده باشه 😁). دفترچه راهنما تمام ویژگیهای خودرو را توصیف میکند، بنابراین جزئیات موجود در دفترچه راهنما در مدلهای مختلف متفاوت است. به همین دلیل است که استفاده مجدد از یک فرآیند ساخت و ساز موجود برای خودروهای واقعی و دستورالعملهای مربوطه آنها منطقی است. البته، ساختن کتابچه راهنمای مثل ساختن یک ماشین نیست، و به همین دلیل است که ما باید کلاس سازنده دیگری را ارائه دهیم که متخصص در نوشتن کتابچه راهنما باشد. این کلاس همان متدهای ساختمانی را مانند برادر خود در زمینه خودروسازی پیادهسازی میکند، اما به جای ساخت قطعات خودرو، آنها را توصیف میکند. با انتقال این سازندهها به یک شی Director مشترک، میتوانیم یک ماشین یا یک کتابچه راهنما بسازیم.
// Using the Builder pattern makes sense only when your products
// are quite complex and require extensive configuration. The
// following two products are related, although they don't have
// a common interface.
class Car is
// A car can have a GPS, trip computer and some number of
// seats. Different models of cars (sports car, SUV,
// cabriolet) might have different features installed or
// enabled.
class Manual is
// Each car should have a user manual that corresponds to
// the car's configuration and describes all its features.
// The builder interface specifies methods for creating the
// different parts of the product objects.
interface Builder is
method reset()
method setSeats(...)
method setEngine(...)
method setTripComputer(...)
method setGPS(...)
// The concrete builder classes follow the builder interface and
// provide specific implementations of the building steps. Your
// program may have several variations of builders, each
// implemented differently.
class CarBuilder implements Builder is
private field car:Car
// A fresh builder instance should contain a blank product
// object which it uses in further assembly.
constructor CarBuilder() is
this.reset()
// The reset method clears the object being built.
method reset() is
this.car = new Car()
// All production steps work with the same product instance.
method setSeats(...) is
// Set the number of seats in the car.
method setEngine(...) is
// Install a given engine.
method setTripComputer(...) is
// Install a trip computer.
method setGPS(...) is
// Install a global positioning system.
// Concrete builders are supposed to provide their own
// methods for retrieving results. That's because various
// types of builders may create entirely different products
// that don't all follow the same interface. Therefore such
// methods can't be declared in the builder interface (at
// least not in a statically-typed programming language).
//
// Usually, after returning the end result to the client, a
// builder instance is expected to be ready to start
// producing another product. That's why it's a usual
// practice to call the reset method at the end of the
// `getProduct` method body. However, this behavior isn't
// mandatory, and you can make your builder wait for an
// explicit reset call from the client code before disposing
// of the previous result.
method getProduct():Car is
product = this.car
this.reset()
return product
// Unlike other creational patterns, builder lets you construct
// products that don't follow the common interface.
class CarManualBuilder implements Builder is
private field manual:Manual
constructor CarManualBuilder() is
this.reset()
method reset() is
this.manual = new Manual()
method setSeats(...) is
// Document car seat features.
method setEngine(...) is
// Add engine instructions.
method setTripComputer(...) is
// Add trip computer instructions.
method setGPS(...) is
// Add GPS instructions.
method getProduct():Manual is
// Return the manual and reset the builder.
// The director is only responsible for executing the building
// steps in a particular sequence. It's helpful when producing
// products according to a specific order or configuration.
// Strictly speaking, the director class is optional, since the
// client can control builders directly.
class Director is
// The director works with any builder instance that the
// client code passes to it. This way, the client code may
// alter the final type of the newly assembled product.
// The director can construct several product variations
// using the same building steps.
method constructSportsCar(builder: Builder) is
builder.reset()
builder.setSeats(2)
builder.setEngine(new SportEngine())
builder.setTripComputer(true)
builder.setGPS(true)
method constructSUV(builder: Builder) is
// ...
// The client code creates a builder object, passes it to the
// director and then initiates the construction process. The end
// result is retrieved from the builder object.
class Application is
method makeCar() is
director = new Director()
CarBuilder builder = new CarBuilder()
director.constructSportsCar(builder)
Car car = builder.getProduct()
CarManualBuilder builder = new CarManualBuilder()
director.constructSportsCar(builder)
// The final product is often retrieved from a builder
// object since the director isn't aware of and not
// dependent on concrete builders and products.
Manual manual = builder.getProduct()
بخش آخر واکشی شیء به دست آمده است. یک ماشین فلزی و یک دفترچه راهنمای کاغذی، اگرچه مرتبط هستند، اما هنوز چیزهای بسیار متفاوتی هستند. ما نمیتوانیم روشی را برای واکشی نتایج در Director بدون پیوند Director با کلاسهای محصول مشخص قرار دهیم. از این رو، نتیجه ساخت و ساز را از سازندهای که کار را انجام داده به دست میآوریم.
قابلیتها و کاربردها
- از الگوی Builder برای خلاص شدن از شر “سازنده تلسکوپ” استفاده کنید. فرض کنید سازندهای با ده پارامتر در اختیاری دارید. صدا زدن چنین جانوری بسیار ناخوشایند است. بنابراین، سازنده را بیش از حد بارگذاری میکنید و چندین نسخه کوتاهتر با پارامترهای کمتر ایجاد میکنید. این سازندهها همچنان به سازنده اصلی ارجاع میدهند و برخی از مقادیر پیش فرض را به هر پارامتر حذف شده منتقل میکنند.
class Pizza {
Pizza(int size) { ... }
Pizza(int size, boolean cheese) { ... }
Pizza(int size, boolean cheese, boolean pepperoni) { ... }
// ...
الگوی Builder به شما امکان میدهد اشیاء را گام به گام بسازید و فقط از مراحلی استفاده کنید که واقعاً به آنها نیاز دارید. پس از پیادهسازی الگو، دیگر لازم نیست دهها پارامتر را در سازندههای خود جمع کنید.
- زمانی که میخواهید کد شما بتواند نمایشهای مختلفی از یک محصول (مثلا خانههای سنگی و چوبی) ایجاد کند، از الگوی سازنده استفاده کنید. الگوی سازنده را میتوان زمانی به کار برد که ساخت نمایشهای مختلف محصول شامل مراحل مشابهی باشد که فقط در جزئیات متفاوت هستند. اینترفیس سازنده پایه تمام مراحل ساخت و ساز ممکن را تعریف میکند و سازندگان concrete این مراحل را برای ساختن نمایشهای خاصی از محصول اجرا میکنند. در ضمن کلاس Director ترتیب ساخت را هدایت میکند.
- از الگوی Builder برای ساخت درختان مرکب یا سایر اشیاء پیچیده استفاده کنید. این الگو به شما امکان میدهد محصولات را مرحله به مرحله بسازید. شما میتوانید اجرای برخی از مراحل را بدون شکستن محصول نهایی به تعویق بیندازید. شما حتی میتوانید مراحل را به صورت بازگشتی فراخوانی کنید، که در مواقعی که نیاز به ساخت درخت شی دارید به کارتان میآید. یک سازنده هنگام اجرای مراحل ساخت، محصول ناتمام را در معرض دید قرار نمیدهد. این کار از دریافت یک نتیجه ناقص توسط کد مشتری جلوگیری میکند.
نحوه پیادهسازی
- اطمینان حاصل کنید که میتوانید به وضوح مراحل ساخت و ساز مشترک را برای ساختن تمام نمایشهای محصول موجود تعریف کنید. در غیر این صورت، نمیتوانید به اجرای الگو ادامه دهید.
- این مراحل را در اینترفیس سازنده پایه اعلام کنید.
- یک کلاس سازنده concrete برای هر یک از نمایشهای محصول ایجاد کنید و مراحل ساخت آنها را اجرا کنید. اجرای روشی برای واکشی نتیجه ساخت و ساز را فراموش نکنید. دلیل عدم اعلام(declare) این روش در داخل اینترفیس سازنده این است که سازندگان مختلف ممکن است محصولاتی بسازند که اینترفیس مشترکی ندارند. بنابراین، شما نمیدانید که نوع بازگشت برای چنین روشی چه خواهد بود. با این حال، اگر با محصولاتی از یک سلسله مراتب سر و کار دارید، روش واکشی را میتوان با خیال راحت به اینترفیس پایه اضافه کرد.
- به ایجاد یک کلاس Director فکر کنید. ممکن است راههای مختلفی را برای ساختن یک محصول با استفاده از یک شی سازنده یکسان در بر بگیرد.
- کد کلاینت هم اشیاء سازنده و هم Director را ایجاد میکند. قبل از شروع ساخت و ساز، مشتری باید یک شی سازنده را به Director ارسال کند. معمولاً کلاینت این کار را فقط یک بار از طریق پارامترهای سازنده کلاس Director انجام میدهد. خود Director در تمام ساخت و سازهای بعدی از شی سازنده استفاده میکند. یک رویکرد جایگزین وجود دارد، که در آن سازنده به روش ساخت محصول خاص Director منتقل میشود.
- نتیجه ساخت و ساز را میتوان مستقیماً از Director فقط در صورتی به دست آورد که همه محصولات از یک اینترفیس استفاده کنند. در غیر این صورت، مشتری باید نتیجه را از سازنده دریافت کند.
مزایا و معایب الگوی Builder
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- میتوانید اشیاء را گام به گام بسازید، مراحل ساخت را به تعویق بیندازید یا مراحل را به صورت بازگشتی اجرا کنید.
- میتوانید از همان کد ساخت و ساز در هنگام ساختن نمایشهای مختلف محصولات استفاده مجدد کنید.
- اصل مسئولیت واحد: میتوانید کد ساخت و ساز پیچیده را از منطق تجاری محصول جدا کنید.
– معایب
- پیچیدگی کلی کد افزایش مییابد زیرا الگو به ایجاد چندین کلاس جدید نیاز دارد.
ارتباط با الگوهای دیگر
- بسیاری از طرحها با استفاده از متد Factory (کمتر پیچیدهتر و قابل تنظیمتر از طریق زیر کلاسها) شروع میشوند و به سمت الگوهای Abstract Factory، Prototype یا Builder (انعطافپذیرتر، اما پیچیدهتر) تکامل مییابند.
- الگوی Builder بر ساختن اشیاء پیچیده گام به گام تمرکز میکند. اما Abstract Factory در ایجاد خانواده از اشیاء مرتبط تخصص دارد. Abstract Factory محصول را فوراً برمیگرداند، در حالی که Builder به شما امکان میدهد قبل از واکشی محصول، مراحل ساخت و ساز اضافی را اجرا کنید.
- میتوانید هنگام ایجاد درختهای Composite پیچیده از Builder استفاده کنید زیرا میتوانید مراحل ساخت آن را طوری برنامهریزی کنید که به صورت بازگشتی کار کند.
- می توانید Builder را با الگوی Bridge ترکیب کنید: کلاس Director نقش انتزاع را بازی میکند، در حالی که سازندههای مختلف به عنوان پیادهسازی عمل میکنند.
- الگوهای Abstract Factory ، الگوی Builder و Prototypes همگی میتوانند بهعنوان Singletons پیادهسازی شوند.