زمان تخمینی مطالعه: 14 دقیقه

الگوی Builder یک الگوی طراحی Creational است که به شما امکان می‌دهد اشیاء پیچیده را مرحله به مرحله بسازید. این الگو به شما اجازه می‌دهد تا انواع و نمایش‌های مختلف یک شی را با استفاده از کد ساخت یکسان تولید کنید.

بیان مسئله: یک شی پیچیده را تصور کنید که نیاز به مقداردهی اولیه پر زحمت و گام به گام بسیاری از فیلدها و اشیاء تو در تو دارد. این چنین کد اولیه‌ای معمولاً در داخل یک سازنده بزرگ با پارامترهای زیادی مدفون یا حتی بدتر در سراسر کد مشتری پراکنده می‌گردد.

Builder design pattern

به عنوان مثال، بیایید به نحوه ایجاد یک شی House فکر کنیم. برای ساختن یک خانه ساده، باید چهار دیوار و یک طبقه بسازید، یک در نصب کنید، یک جفت پنجره را بسازید و یک سقف بسازید. اما اگر خانه‌ای بزرگ‌تر و روشن‌تر، با حیاط خلوت و وسایل دیگر (مانند سیستم گرمایش، لوله‌کشی، و سیم‌کشی برق) می‌خواهید چه؟

Lots of subclasses create another problem

ساده ترین راه حل گسترش کلاس House پایه و ایجاد مجموعه‌ای از زیر کلاس‌ها برای پوشش تمام ترکیبات پارامترها است. اما در نهایت شما با تعداد قابل توجهی از زیر کلاس‌ها مواجه خواهید شد. هر پارامتر جدید، مانند سبک و مدل ایوان ساختمان، به رشد بیشتر این سلسله مراتب نیاز دارد.

رویکرد دیگری وجود دارد که شامل پرورش زیر کلاس‌ها نمی‌شود. شما می‌توانید یک سازنده غول پیکر درست در کلاس پایه House با تمام پارامترهای ممکن که شی خانه را کنترل می‌کند ایجاد کنید. در حالی که این رویکرد در واقع نیاز به زیر کلاس‌ها را از بین می‌برد، ولی مشکل دیگری هم ایجاد می‌کند.

The telescoping constructor

در اغلب موارد، بیشتر پارامترها استفاده نمی‌شوند و فراخوانی سازنده را بسیار زشت می‌کند. به عنوان مثال، تنها کسری از خانه‌ها دارای استخر شنا هستند، بنابراین پارامترهای مربوط به استخرهای شنا از هر ده بار 9 بار بی‌فایده خواهند بود.

الگوی Builder پیشنهاد می‌کند که کد سازنده شی را از کلاس خودش استخراج کنید و آن را به اشیاء جداگانه‌ای به نام builder ها منتقل کنید.

Applying the Builder pattern

الگوی ساخت شی را در مجموعه‌ای از مراحل (buildWalls، buildDoor و غیره) سازماندهی می‌کند. برای ایجاد یک شی، یک سری از این مراحل را روی یک شی builder اجرا می‌کنید. بخش مهم کار این است که شما نیازی به فراخوانی همه مراحل ندارید. شما فقط می‌توانید مراحلی را فراخوانی کنید که برای ایجاد پیکربندی خاصی از یک شی ضروری هستند. برخی از مراحل ساخت و ساز ممکن است نیاز به اجرای متفاوتی داشته باشند، زمانی که شما نیاز به ساخت نمایش‌های مختلف از محصول دارید. به عنوان مثال، دیوارهای یک کابین ممکن است از چوب ساخته شوند، اما دیوارهای قلعه باید با سنگ ساخته شوند.

در این حالت، می‌توانید چندین کلاس builder مختلف ایجاد کنید که مجموعه مراحل ساختمانی یکسانی را به شیوه‌ای متفاوت اجرا می‌کنند. سپس می‌توانید از این builder ها در فرآیند ساخت (یعنی مجموعه‌ای از فراخوان‌های مرتب شده به مراحل ساختمان) برای تولید انواع مختلف اشیا استفاده کنید.

برای مثال، سازنده‌ای(builder) را تصور کنید که همه چیز را از چوب و شیشه می‌سازد، سازنده دومی هم وجود دارد که همه چیز را با سنگ و آهن می‌سازد و همچنین سازنده سومی هم هست که از طلا و الماس استفاده می‌کند. با فراخوانی همان مجموعه گام‌ها، از سازنده اول یک خانه معمولی، از سازنده دوم یک قلعه کوچک و از سومی یک قصر دریافت می‌کنید. با این حال، این تنها در صورتی کار می‌کند که کد کلاینت که مراحل ساختمان را فراخوانی می‌کند، بتواند با استفاده از یک اینترفیس مشترک با سازنده‌ها تعامل داشته باشد.

کلاس Director

برای استفاده از الگوی Builder می‌توانید گام را فراتر گذاشته و یک سری فراخوان به مراحل سازنده‌ای که برای ساختن یک محصول استفاده می‌کنید در یک کلاس جداگانه به نام Director استخراج کنید. کلاس Director ترتیب اجرای مراحل ساختمان را مشخص می‌کند، در حالی که سازنده اجرای آن مراحل را ارائه می‌دهد.

داشتن یک کلاس Director در برنامه مورد استفاده شما به شدت ضروری نیست. همیشه می‌توانید مراحل ساخت را به ترتیبی خاص مستقیماً از کد مشتری فراخوانی کنید. با این حال، کلاس Director ممکن است مکان خوبی برای قرار دادن روتین‌های مختلف ساخت و ساز باشد تا بتوانید از آنها در سراسر برنامه خود استفاده مجدد کنید. علاوه بر این، کلاس Director جزئیات ساخت محصول را به طور کامل از کد مشتری پنهان می‌کند. مشتری فقط باید یک سازنده را با یک Director مرتبط کند، ساخت و ساز را با مدیر(Director) راه اندازی کند و نتیجه را از سازنده بگیرد.

برای تشریح بیشتر و روشن شدن موضوع بر اساس اصول مهندسی نرم افزار ساختار این الگوی طراحی را به صورت UML در تصویر زیر نشان داده‌ایم همچنین توضیحات هر گام به طور مشخص در زیر این تصویر نوشته شده است.

شبه کد(Pseudocode)

این مثال از الگوی Builder نشان می‌دهد که چگونه می‌توانید از کد ساخت‌وساز شی مشابه در هنگام ساخت انواع مختلف محصولات، مانند اتومبیل، استفاده مجدد کنید و کتابچه راهنمای مربوطه را برای آنها ایجاد کنید.

The structure of the Builder pattern example

ماشین یک شی پیچیده است که می‌توان آن را به صدها روش مختلف ساخت. به جای اینکه کلاس 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 با کلاس‌های محصول مشخص قرار دهیم. از این رو، نتیجه ساخت و ساز را از سازنده‌ای که کار را انجام داده به دست می‌آوریم.

قابلیت‌ها و کاربرد‌ها

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    // ...

الگوی Builder به شما امکان می‌دهد اشیاء را گام به گام بسازید و فقط از مراحلی استفاده کنید که واقعاً به آنها نیاز دارید. پس از پیاده‌سازی الگو، دیگر لازم نیست ده‌ها پارامتر را در سازنده‌های خود جمع کنید.

نحوه پیاده‌سازی

  1. اطمینان حاصل کنید که می‌توانید به وضوح مراحل ساخت و ساز مشترک را برای ساختن تمام نمایش‌های محصول موجود تعریف کنید. در غیر این صورت، نمی‌توانید به اجرای الگو ادامه دهید.
  2. این مراحل را در اینترفیس سازنده پایه اعلام کنید.
  3. یک کلاس سازنده concrete برای هر یک از نمایش‌های محصول ایجاد کنید و مراحل ساخت آنها را اجرا کنید. اجرای روشی برای واکشی نتیجه ساخت و ساز را فراموش نکنید. دلیل عدم اعلام(declare) این روش در داخل اینترفیس سازنده این است که سازندگان مختلف ممکن است محصولاتی بسازند که اینترفیس مشترکی ندارند. بنابراین، شما نمی‌دانید که نوع بازگشت برای چنین روشی چه خواهد بود. با این حال، اگر با محصولاتی از یک سلسله مراتب سر و کار دارید، روش واکشی را می‌توان با خیال راحت به اینترفیس پایه اضافه کرد.
  4. به ایجاد یک کلاس Director فکر کنید. ممکن است راه‌های مختلفی را برای ساختن یک محصول با استفاده از یک شی سازنده یکسان در بر بگیرد.
  5. کد کلاینت هم اشیاء سازنده و هم Director را ایجاد می‌کند. قبل از شروع ساخت و ساز، مشتری باید یک شی سازنده را به Director ارسال کند. معمولاً کلاینت این کار را فقط یک بار از طریق پارامترهای سازنده کلاس Director انجام می‌دهد. خود Director در تمام ساخت و سازهای بعدی از شی سازنده استفاده می‌کند. یک رویکرد جایگزین وجود دارد، که در آن سازنده به روش ساخت محصول خاص Director منتقل می‌شود.
  6. نتیجه ساخت و ساز را می‌توان مستقیماً از Director فقط در صورتی به دست آورد که همه محصولات از یک اینترفیس استفاده کنند. در غیر این صورت، مشتری باید نتیجه را از سازنده دریافت کند.

مزایا و معایب الگوی Builder

این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:

– مزایا

– معایب

ارتباط با الگوهای دیگر

نمونه کد الگوی Builder

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *