خوب خوب خوب! بریم برای الگوی دوم که من خیلی دوسش دارم و میشه گفت کمی متفاوت از چهار الگو دیگه در این بخش هست🤓! نکته ای که وجود داره من جلو بعضی کلمات فارسی، انگلیسی اونها رو هم نوشتم که معنی اصلی گم نشه و یه جورایی هم تاکید بر این باشه که این موارد مهم هست. خوب قهوه یا چایی رو برای خودتون بیارید و کمربندها رو ببندید که راننده آماده رفتن به سمت پارت جدید یعنی بخش Builder هست🤓

هدف:

ساخت (construction) یک شی پیچیده را از نمایش (representation) آن جدا می شود تا با فرآیند ساخت (construction process) یکسان بتوان نمایش های (representation) متفاوتی ایجاد کرد.

طرح مسئله:

یک ابزار (خواننده) و مفسر برای قالب تبادل سند RTF (یا Rich Text Format که تیم مایکروسافت از سال 1980 برای word شروع به توسعه آن کرد و در سال 1987 و در زمان تولد من👶🐥 اولین نسخه آن را ریلیز داد) باید بتواند RTF را به بسیاری از فرمت های متنی تبدیل کند. خواننده ممکن است اسناد RTF را به متن ASCII ساده یا به یک ویجت متنی تبدیل کند که می تواند به صورت interactive ویرایش شود. با این حال، مشکل این است که تعداد تبدیل‌های احتمالی پایان باز دارد.

راه حل این است که کلاس RTFReader از یک شی TextConverter استفاده کند تا بتواند RTF را به نمایش متنی دیگری تبدیل کند. همانطور که RTFReader  سند را تجزیه (parse) می کند، از TextConverter برای انجام تبدیل استفاده می کند. هر زمان که RTFReader یک Token (اصطلاح توکن به unit of text یا control word برای اسناد RTF اشاره دارد که نیاز به پردازش دارد) RTF (چه plain text  یا یک RTF control word) را تشخیص دهد، درخواستی را برای تبدیل توکن به TextConverter ارسال می کند. اشیاء TextConverter هم مسئول انجام تبدیل داده و همچنین برای نمایش توکن در یک فرمت خاص هستند.

زیر کلاس های TextConverter در تبدیل ها و فرمت های مختلف آگاهی دارد. به عنوان مثال، یک ASCIIConverter، درخواست‌ها را برای تبدیل هر چیزی به جز plain text نادیده می‌گیرد. از سوی دیگر، یک TeXConverter (یک فرمت متنی به نام Tex در بازه 1970 تا 1980 توسعه پیدا کرد که به عنوان یک استاندارد در ریاضیات و فیزیک مورد استفاده قرار گرفت و اگر تونستید یه مطالعه درموردش داشته باشید چیز باحالیه🤓) متدهایی را برای همه درخواست‌ها اجرا می‌کند تا یک نمایش TeX تولید کند که تمام اطلاعات استایلی را در متن ثبت می کند. یک TextWidgetConverter یک شیء رابط کاربری پیچیده تولید می کند که به کاربر اجازه می دهد متن را ببیند و ویرایش کند.

هر نوع کلاسconverter مکانیزمی را برای ایجاد و assemble کردن یک شی پیچیده دریافت می کند و آن را در پشت یک abstract interface قرار می دهد. converter جدا از reader است که مسئول تجزیه یک سند RTF است.

Builder pattern همه این روابط را در بر می گیرد. هر کلاس converter در الگو یک builder و هر reader را director می نامند. در این مثال، Builder pattern الگوریتم تفسیر قالب متنی (یعنی parser اسناد RTF) را از نحوه ایجاد و نمایش یک قالب تبدیل شده جدا می کند. این به ما امکان می دهد از الگوریتم تجزیه RTFReader برای ایجاد نمایش های متنی مختلف از اسناد RTF استفاده مجدد کنیم فقط RTFReader را با زیر کلاس های مختلف TextConverter پیکربندی و ایجاد کنید.

کاربرد:

زمانی که از الگوی Builder استفاده کنید:

یک الگوریتم مشخص برای ایجاد یک شیء پیچیده، باید مستقل از اجزای سازنده شی و نحوه و assemble آنها باشد.

construction process می بایست اجازه یا دسترسی به representation های مختلف برای شی ساخته شده را بدهد.

شرکت کنندگان (اجزای این الگو):

Builder (TextConverter):

یک رابط انتزاعی که برای ایجاد آبجکت مورد نظر می باشد.

‏ConcreteBuilder (ASCIIConverter، TeXConverter، TextWidgetConverter) :

بخش هایی از آبجکت مورد نظر را با رابط Builder پیاده سازی و assemble می کند.

یک رابط برای دریافت آبجکت مورد نظر ارائه می دهد (به عنوان مثال GetASCIIText، GetTextWidget).‏

Director (RTFReader):

با استفاده از رابط Builder یک شی را می سازد.

Product (ASCIIText, TeXText, TextWidget):

نشان دهنده شی پیچیده در حال ساخت است. ConcreteBuilder درحقیقت representation داخلی آبجکت مورد نظر را می سازد و فرآیند assemble کردن آن را تعریف می کند.

شامل کلاس هایی است که اجزای تشکیل دهنده را تعریف می کنند، از جمله رابط های برای assemble  کردن قطعات برای یک نتیجه نهایی.

 

همکاری ها:

client آبجکت Director را ایجاد می کند و آن را با شی Builder مورد نظر پیکربندی می کند.

هر زمان که بخشی از آبجکت مدنظر باید ساخته شود، Director به Builder اطلاع می دهد.

Builder به درخواست های director رسیدگی می کند و قطعاتی را به آبجکت مدنظر اضافه می کند.

client آبجکت مدنظر را از سازنده دریافت می کند.

 

در interaction diagram زیر نحوه همکاری Builder و Director  با client را نشان می دهد.

مزایا و معایب:

الگوی Builder دارای مزایا و معایب زیر است:

به شما این امکان را می دهد که نمایش داخلی (representation) یک آبجکت مدنظر را تغییر دهید. شی Builder یک رابط انتزاعی برای ساخت آبجکت مدنظر، در اختیار director قرار می دهد. رابط به builder اجازه می دهد تا representation و internal structure آبجکت مد نظر را پنهان کند. همچنین نحوه assemble کردن آبجکت را پنهان می کند. از آنجا که آبجکت از طریق یک رابط انتزاعی ساخته شده است، تنها کاری که باید انجام دهید برای تغییر نمایش داخلی آبجکت، تعریف نوع جدیدی از builder است.

کد را برای construction و representation جدا می کند. الگویBuilder همچنین modularity را از طریق encapsulate کردن بهبود می بخشد و روشی را ارئه می دهد که یک شی پیچیده، ساخته و نمایش داده شود. Client ها نیازی به دانستن هیچ چیز در مورد کلاس هایی که ساختار داخلی آبجکت را تعریف می کنند، ندارند. چنین کلاس هایی در رابط Builder ظاهر نمی شوند.

هر ConcreteBuilder شامل تمام کدهایی برای ایجاد و assemble نوع خاصی از آبجکت است. کد یک بار نوشته می شود سپس Director های مختلف می توانند از آن برای ساخت انواع آبجکت ها و از همان مجموعه قطعات استفاده مجدد کنند. در مثال قبلی RTF، می‌توانیم یک reader برای قالبی غیر از RTF تعریف کنیم، مثلاً یک SGMLReader، و از همان TextConverter برای تولید ASCIIText ،TeXText و TextWidget از اسناد SGML (یا Standard Generalized Markup Language که یک روش دقیق برای ذخیره رکوردها و فیلدها بوده است) استفاده کنیم.

این به شما کنترل دقیق تری بر روند ساخت و ساز (construction process) می دهد. برخلاف الگوهای creational که آبجکت های مورد نظر را در یک شات می‌سازند، الگوی Builder آبجکت های مد نظر را گام به گام تحت کنترل director می‌سازد. تنها زمانی که آبجکت نهایی تمام شود، director آن را از Builder تحویل می گیرد. از این رو رابط سازنده فرآیند ساخت آبجکت را بیش از سایر الگوهای creational منعکس می کند. این امر به شما کنترل دقیق تری بر روند ساخت و در نتیجه ساختار داخلی آبجکت نهایی می دهد.

پیاده سازی:

به طور معمول یک کلاس Builder انتزاعی وجود دارد که یک operation را برای هر جزء تعریف می کند و director ممکن است از آن بخواهد آبجکت را ایجاد کند. Operation ها به طور پیش فرض هیچ کاری انجام نمی دهند. یک کلاس ConcreteBuilder ،operation مربوطه را برای اجزایی که علاقه مند به ایجاد آن هستن override می کند.

در اینجا برخی موارد در زمان پیاده سازی وجود دارد که باید در نظر بگیرید:

رابط Assemble و construction. ها آبجکت های خود را به صورت گام به گام می سازند. بنابراین رابط کلاس Builder باید به اندازه کافی عمومی باشد تا امکان ساخت محصولات را برای انواع concrete builder ها فراهم کند.

یک مسئله کلیدی طراحی مربوط به مدل برای فرآیند ساخت و assemble کردن است. مدلی که در آن نتایج درخواست های ساخت و ساز به سادگی به محصول الحاق می شود، معمولاً کافی است. در مثال RTF،builder در اصل token بعدی را به متنی که convert کرده است تبدیل و اضافه میکند.

اما گاهی اوقات ممکن است نیاز داشته باشید به بخش هایی از آبجکت که قبلا ساخته شده اند دسترسی داشته باشید. در مثال Maze که در نمونه کد ارائه می‌کنیم، رابط MazeBuilder به شما امکان می‌دهد یک “در” بین اتاق‌های موجود اضافه کنید. ساختارهای درختی مانند parse tree ها (درختی منظم و ریشه دار است که ساختار نحوی یک رشته را بر اساس برخی دستور زبان های بدون متن نشان می دهد) که از پایین به بالا ساخته می شوند نمونه دیگری هستند. در آن صورت، سازنده گره‌های فرزند را به director برمی‌گرداند، و سپس آن‌ها را برای ساختن گره‌های والد به سازنده برمی‌گرداند.

چرا کلاس انتزاعی برای آبجکت های نهایی وجود ندارد؟ در حالت رایج، آبجکت های تولید شده توسط concrete builder ها به قدری در نمایش آنها تفاوت دارند که از دادن یک کلاس اصلی به آبجکت های مختلف سود چندانی وجود ندارد. در مثال RTF، اشیاء ASCIIText و TextWidget بعید است که یک رابط مشترک داشته باشند، و همچنین نیازی به آن ندارند. از آنجایی کهclient معمولاً director را با سازنده concrete builder پیکربندی می کند، client در موقعیتی قرار دارد که بداند کدام زیر کلاس Builder از Builder در حال استفاده است و می تواند آبجکت های خود را مطابق با آن مدیریت کند.

کد نمونه

ما یک نوع جدید از اعضای تابع CreateMaze را تعریف می کنیم که کلاس MazeBuilder را به عنوان آرگومان می گیرد.

کلاس MazeBuilder رابط زیر را برای maze ها تعریف می کند:

public class MazeBuilder
{
    public virtual void BuildMaze() { }
    public virtual void BuildRoom(int room) { }
    public virtual void BuildDoor(int roomFrom, int roomTo) { }

    public virtual Maze? GetMaze() { return null; }
    protected MazeBuilder() { }
}

این رابط می تواند سه چیز ایجاد کند:

1) maze

2) اتاق هایی با شماره اتاق خاص

3) درهای بین اتاق های شماره گذاری شده

متد GetMaze یک maze را به client برمی گرداند. زیر کلاس های MazeBuilder این متدها را override می کنند تا maze ساخته شده را برگردانند.

تمام متدهای ساخت تودرتو MazeBuilder به طور پیش فرض هیچ کاری انجام نمی دهد. آنها virtual تعریف می شوند تا اجازه دهند کلاس های مشتق شده override را برروی آنها راحت انجام دهند.

با توجه به رابط MazeBuilder، می توانیم تابع عضو CreateMaz را تغییر دهیم تا این سازنده را به عنوان یک پارامتر در نظر بگیریم.

public class MazeGame
{
    public Maze CreateMaze(MazeBuilder builder)
    {
        builder.BuildMaze();

        builder.BuildRoom(1);
        builder.BuildRoom(2);
        builder.BuildDoor(1, 2);

        return builder.GetMaze();
    }

    public Maze CreateComplexMaze(MazeBuilder builder)
    {
        builder.BuildRoom(1);
        // ...
        builder.BuildRoom(1001);

        return builder.GetMaze();
    }
}

مانند سایر الگوهای creational، الگوی Builder نحوه ایجاد اشیاء را در این مورد از طریق رابط تعریف شده توسط MazeBuilder، encapsulate می کند. این بدان معناست که ما می توانیم از MazeBuilder برای ساخت انواع مختلف مارپیچ ها استفاده مجدد کنیم. عملیات CreateComplexMaze همین مثال را ارائه میدهد.

توجه داشته باشید که MazeBuilder خود maze ایجاد نمی کند. هدف اصلی آن فقط تعریف یک رابط برای ایجاد maze ها است. پیاده سازی های خالی را در درجه اول برای راحتی تعریف می کند. زیر کلاس های MazeBuilder کار واقعی را انجام می دهند.

زیر کلاس StandardMazeBuilder پیاده سازی است که تودرتو های ساده می سازد همچنین تودرتو را که در متغیر currentMaze می سازد را پیگیری می کند.

public class StandardMazeBuilder : MazeBuilder
{
    private Maze _currentMaze;

    public StandardMazeBuilder()
    {
        _currentMaze = null;
    }

    public override void BuildMaze()
    {
        _currentMaze = new Maze();
    }

    public override Maze GetMaze()
    {
        return _currentMaze;
    }

    public override void BuildRoom(int n)
    {
        if (_currentMaze.RoomNo(n) == null)
        {
            Room room = new Room(n);
            _currentMaze.AddRoom(room);

            room.SetSide(Direction.North, new Wall());
            room.SetSide(Direction.South, new Wall());
            room.SetSide(Direction.East, new Wall());
            room.SetSide(Direction.West, new Wall());
        }
    }

    public override void BuildDoor(int n1, int n2)
    {
        Room r1 = _currentMaze.RoomNo(n1);
        Room r2 = _currentMaze.RoomNo(n2);
        Door d = new Door(r1, r2);

        r1.SetSide(CommonWall(r1, r2), d);
        r2.SetSide(CommonWall(r2, r1), d);
    }

    private Direction CommonWall(Room r1, Room r2)
    {
        // Implementation of CommonWall method
        return Direction.North; // Placeholder
    }
}

Client ها اکنون می توانند از CreateMaze در ارتباط با StandardMazeBuilder برای ایجاد یک تودرتو استفاده کنند:

Maze maze;
MazeGame game = new MazeGame();
StandardMazeBuilder builder = new StandardMazeBuilder();
game.CreateMaze(builder);
maze = builder.GetMaze();

 

ما می توانستیم تمام متدهای StandardMazeBuilder را در Maze قرار دهیم و اجازه دهیم هر Maze خودش را بسازد. اما کوچک‌تر کردن Maze درک و اصلاح آن را آسان‌تر می‌کند و StandardMazeBuilder به راحتی از Maze جدا می‌شود. مهم‌تر از همه جدا کردن این دو به شما امکان می‌دهد تا انواع MazeBuilder داشته باشید که هر کدام از کلاس‌های متفاوتی برای اتاق‌ها، دیوارها و درها استفاده می‌کنند.

از ‏MazeBuilder عجیب‌تر CountingMazeBuilder است. این builder یه maze ایجاد نمی کند فقط انواع مختلفی از component هایی را که می‌توانستند ایجاد شوند شمارش می‌کند.

public class CountingMazeBuilder : MazeBuilder
{
    private int _doors;
    private int _rooms;

    public CountingMazeBuilder()
    {
        _rooms = _doors = 0;
    }

    public override void BuildRoom(int room)
    {
        _rooms++;
    }

    public override void BuildDoor(int roomFrom, int roomTo)
    {
        _doors++;
    }

    public void GetCounts(out int rooms, out int doors)
    {
        rooms = _rooms;
        doors = _doors;
    }
}

در اینجا نحوه استفاده client از CountingMazeBuilder آمده است:

int rooms, doors;
MazeGame game2 = new MazeGame();
CountingMazeBuilder builder2 = new CountingMazeBuilder();

game2.CreateMaze(builder2);
builder2.GetCounts(out rooms, out doors);

Console.WriteLine($"The maze has {rooms} rooms and {doors} doors");

و در نهایت کد پایانی ما به شکل زیر می باشد:

public enum Direction { North, South, East, West }


public abstract class MapSite
{
    public abstract void Enter();
}



public class Room : MapSite
{
    private MapSite[] _sides = new MapSite[4];
    private int _roomNumber;

    public Room(int roomNo)
    {
        _roomNumber = roomNo;
    }

    public MapSite GetSide(Direction direction)
    {
        return _sides[(int)direction];
    }

    public void SetSide(Direction direction, MapSite mapSite)
    {
        _sides[(int)direction] = mapSite;
    }

    public override void Enter()
    {
        // Implementation for entering the room
    }
}


public class Wall : MapSite
{
    public Wall() { }

    public override void Enter() { }
}

public class Door : MapSite
{
    private Room _room1;
    private Room _room2;
    private bool _isOpen;

    public Door(Room room1 = null, Room room2 = null)
    {
        _room1 = room1;
        _room2 = room2;
        _isOpen = false;
    }

    public override void Enter() { }

    public Room OtherSideFrom(Room room)
    {
        return room == _room1 ? _room2 : _room1;
    }
}

public class Maze
{
    private List<Room> rooms;

    public Maze()
    {
        rooms = new List<Room>();
    }

    public void AddRoom(Room room)
    {
        rooms.Add(room);
    }

    public Room? RoomNo(int roomNumber)
    {
        return rooms.Count < roomNumber ? null : rooms[roomNumber-1];
    }
}


public class MazeBuilder
{
    public virtual void BuildMaze() { }
    public virtual void BuildRoom(int room) { }
    public virtual void BuildDoor(int roomFrom, int roomTo) { }

    public virtual Maze? GetMaze() { return null; }
    protected MazeBuilder() { }
}

public class MazeGame
{
    public Maze CreateMaze(MazeBuilder builder)
    {
        builder.BuildMaze();

        builder.BuildRoom(1);
        builder.BuildRoom(2);
        builder.BuildDoor(1, 2);

        return builder.GetMaze();
    }

    public Maze CreateComplexMaze(MazeBuilder builder)
    {
        builder.BuildRoom(1);
        // ...
        builder.BuildRoom(1001);

        return builder.GetMaze();
    }
}

public class StandardMazeBuilder : MazeBuilder
{
    private Maze _currentMaze;

    public StandardMazeBuilder()
    {
        _currentMaze = null;
    }

    public override void BuildMaze()
    {
        _currentMaze = new Maze();
    }

    public override Maze GetMaze()
    {
        return _currentMaze;
    }

    public override void BuildRoom(int n)
    {
        if (_currentMaze.RoomNo(n) == null)
        {
            Room room = new Room(n);
            _currentMaze.AddRoom(room);

            room.SetSide(Direction.North, new Wall());
            room.SetSide(Direction.South, new Wall());
            room.SetSide(Direction.East, new Wall());
            room.SetSide(Direction.West, new Wall());
        }
    }

    public override void BuildDoor(int n1, int n2)
    {
        Room r1 = _currentMaze.RoomNo(n1);
        Room r2 = _currentMaze.RoomNo(n2);
        Door d = new Door(r1, r2);

        r1.SetSide(CommonWall(r1, r2), d);
        r2.SetSide(CommonWall(r2, r1), d);
    }

    private Direction CommonWall(Room r1, Room r2)
    {
        // Implementation of CommonWall method
        return Direction.North; // Placeholder
    }
}

public class CountingMazeBuilder : MazeBuilder
{
    private int _doors;
    private int _rooms;

    public CountingMazeBuilder()
    {
        _rooms = _doors = 0;
    }

    public override void BuildRoom(int room)
    {
        _rooms++;
    }

    public override void BuildDoor(int roomFrom, int roomTo)
    {
        _doors++;
    }

    public void GetCounts(out int rooms, out int doors)
    {
        rooms = _rooms;
        doors = _doors;
    }
}

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

Abstract Factory شبیه Builder است که ممکن است اشیاء پیچیده را نیز بسازد. تفاوت اصلی این است که الگوی Builder بر ساختن یک شی پیچیده گام به گام تمرکز می کند. تأکید Abstract Factory بر خانواده‌های آبجکت های مورد نظر (اعم از ساده یا پیچیده) است. Builder آبجکت مد نظر را به عنوان آخرین مرحله برمی گرداند، اما تا آنجا که به الگوی Abstract Factory مربوط می شود، آبجکت مربوطه بلافاصله ساخته و بر میگردد.

الگو Composite چیزی است که اغلب با builder ساخته می شود.

 

خوب اگر موافق باشید سفر ما تا اینجا باشه و بزنیم کنار تا شما یه استراحت بکنید و در قسمت بعد وارد سومین الگو یعنی Factory Method بشیم😉

مرسی که تا اینجا با من همراه بودید راستی نظرات یادتون نره🫡

 

اگر مطالب تا اینجا به دردتون خورده خوشحال میشم با کلیک روی تصویر زیر برای من یه قهوه خوشمزه بگیرید تا مغزم حسابی کیف کنه 😍!!!

دسته بندی شده در: