502
1

מסבירים את תבנית הבנאי (Builder Pattern)

502
זמן קריאה: 5 דקות
תבנית הבנאי היא תבנית עיצוב מקטגוריית הCreational המאפשרת לבנות אובייקטים מורכבים באופן מודולרי ובצעד אחר צעד.
פעמים רבות אנו מפתחים מחלקות שיש להן שדות מרובים, לא תמיד נרצה ליצור את כל השדות באובייקט דרך הConstractor ובאמצעות התבנית הזו נוכל ליצור רק חלק מהשדות לפי הצורך ולפי מה שמתאים לנו בכל עת. 
 
 
דוגמא:
				
					public class OrderBook
{
    public Buyer Buyer { get; set; }
    public Book Book { get; set; }
    public double Price { get; set; }
    public DateTime Date { get; set; }
    (...)
    
    public Order(Buyer buyer, Book book, 
        double price, DateTime date (...))
    {
            Buyer = buyer;
            Date = date;
            Product = product;
            Price = price;
            (...)
    }
}
				
			

כפי שאתם יכולים לראות זה לא יהיה פשוט ליצור אובייקט של הזמנה חדשה עם כמות השדות האלה, במיוחד שחלק מהשדות הן אובייקטים בעצמם ובנוסף – מי יודע לאן האובייקט הזה יתפתח בעתיד?

האם נרצה להיות תלויים בבנאי הקלאסי? כך זה נראה ללא תבנית עיצוב:

				
					Buyer buyer = _buyerRepository.GetById(buyerId);
Book book = _bookRepository.GetById(bookId);

Order order = new Order(buyer, book, price, date);
				
			

כך זה ייראה ע”י שימוש בתבנית Builder

				
					OrderBook order = OrderBuilder.Init(buyerRepository, sellerRepository)
                          .SetBuyer(buyerId)
                          .SetBook(sellerId)
                          .SetDate()
                          .Build();
				
			
				
					public class OrderBuilder
{
    private OrderBook _order = new OrderBook();
    
    private readonly IBuyerRepository _buyerRepository;
    private readonly IBookRepository _bookRepository;
    
    private OrderBuilder(IBuyerRepository buyerRepository,
            IBookRepository bookRepository)
    {
        _buyerRepository = buyerRepository;
        _bookRepository = bookRepository;
    }
    public static OrderBuilder Init(IBuyerRepository buyerRepository, 
            IBookRepository bookRepository)
    {
        return new OrderBuilder(buyerRepository, bookRepository);
    }
    
    public OrderBook Build() => _order;
    
    public OrderBuilder SetBuyer(int buyerId)
    {
        _order.Buyer = _buyerRepository.GetById(buyerId);
        return this;
    }
    
    public OrderBuilder SetBook(int bookId)
    {
        _order.Buyer = _bookRepository.GetById(bookId);
        return this;
    }
    
    public OrderBuilder SetDate()
    {
        _order.PurchaseDate = DateTime.Now;
        return this;
    }
    
    (...)
}
				
			
ההבדלים ניכרים רק על ידי התבוננות בדוגמאות הקוד שהבאתי.
שורה אחת של קוד בהשוואה לשיטה הקלאסית בה אנו חייבים לעשות הכנות ולבנות את האובייקטים הנדרשים מבעוד מועד..
כפי שכבר נאמר – באמצעות תבנית הBuilder אנחנו יכולים לבנות באופן מודולרי ולהשתמש רק בחלק מהשדות לפי הצורך. 
 
חשוב לציין שבסופו של יום שתי הגישות תקינות וטובות והבחירה בין שתי האפשרויות הללו היא רק לפי הגישה שלכם לקוד.
 עם הBuilder תקבלו הרבה סדר וארגון בקוד לטווח הארוך.

מספר בנאים עבור מטרות דומות

בדוגמא הבאה אציג לכם דרך ליצור ספרים – פעם אחת ספר מודפס ובפעם השניה ספר דיגיטלי
בדוגמא הזו ההבדל היחיד בינהם יהיה פורמט הספר שיוגדר אוטומטי וישתנה כתלות בבנאי שתבחרו.

תחילה נממש את המחלקה Book:

				
					
public class Book
{
    public Book(string name, string author, string language, string format, int numOfPages, string publisher, string distributor)
    {
        Name = name;
        Author = author;
        Language = language;
        Format = format;
        NumOfPages = numOfPages;
        Publisher = publisher;
        Distributor = distributor;
    }

    public string Name { get; set; }
    public string Author { get; set; }
    public string Language { get; set; }
    public string Format { get; set; }
    public int NumOfPages { get; set; }
    public string Publisher { get; set; }
    public string Distributor { get; set; }

    public override string ToString()
    {
        var stringBuilder = new StringBuilder();
        stringBuilder.Append("{\n");
        stringBuilder.Append($"\tName: {Name}\n");
        stringBuilder.Append($"\tAuthor: {Author}\n");
        stringBuilder.Append($"\tLanguage: {Language}\n");
        stringBuilder.Append($"\tFormat: {Format}\n");
        stringBuilder.Append($"\tNumOfPages: {NumOfPages}\n");
        stringBuilder.Append($"\tPublisher: {Publisher}\n");
        stringBuilder.Append($"\tDistributor: {Distributor}\n");
        stringBuilder.Append("}");
        return stringBuilder.ToString();
    }
}
				
			

לאחר מכן נגדיר ממשק שממנו ירשו כל סוגי הבנאים ( יצירת ספר דיגיטלי, יצירת ספר מודפס ועוד.. )

				
					    public interface IBookBuilder
    {
        Book Build();
        IBookBuilder SetName(string name);
        IBookBuilder SetAuthor(string author);
        IBookBuilder SetLanguage(string lang);
        IBookBuilder SetFormat();
        IBookBuilder SetNumOfPages(int pages);
        IBookBuilder SetPublisher(string pub);
        IBookBuilder SetDistributor(string dist);
    }
				
			

וכעת היוצר של הספר הדיגיטלי:

				
					public class EBook : IBookBuilder
    {
        Book _book = new Book();
        
        public Book Build() => _book;

        public IBookBuilder SetName(string name)
        {
            _book.Name = name;
            return this;
        }
        public IBookBuilder SetAuthor(string author)
        {
            _book.Author = author;
            return this;
        }
        public IBookBuilder SetLanguage(string lang)
        {
            _book.Language = lang;
            return this;
        }
        public IBookBuilder SetFormat()
        {
            _book.Format = "Electronic";
            return this;
        }
        public IBookBuilder SetNumOfPages(int pages)
        {
            _book.NumOfPages = pages;
            return this;
        }
        public IBookBuilder SetPublisher(string pub)
        {
            _book.Publisher = pub;
            return this;
        }
        public IBookBuilder SetDistributor(string dist)
        {
            _book.Distributor = dist;
            return this;
        }
    }
				
			

כעת אותו הדבר עבור ספר מודפס:

				
					public class PrintedBook : IBookBuilder
    {
        Book _book = new Book();
        public Book Build() => _book;

        public IBookBuilder SetName(string name)
        {
            _book.Name = name;
            return this;
        }
        public IBookBuilder SetAuthor(string author)
        {
            _book.Author = author;
            return this;
        }
        public IBookBuilder SetLanguage(string lang)
        {
            _book.Language = lang;
            return this;
        }
        public IBookBuilder SetFormat()
        {
            _book.Format = "Printed";
            return this;
        }
        public IBookBuilder SetNumOfPages(int pages)
        {
            _book.NumOfPages = pages;
            return this;
        }
        public IBookBuilder SetPublisher(string pub)
        {
            _book.Publisher = pub;
            return this;
        }
        public IBookBuilder SetDistributor(string dist)
        {
            _book.Distributor = dist;
            return this;
        }
    }
				
			

לבסוף תוכנית שתפעיל את הקוד שיצרנו, כאשר ניתן לשים לב שהיא יוצרת את אותו הספר בדיוק עם אותם ארגומנטים וכפי שציינתי קודם ההבדל הוא הפורמט שהשתנה.

				
					public class Program
    {
        const string BOOK_NAME = "Clean Code";
        const string AUTHOR = "Robert Martin";
        const string PUBLISHER = "Pearson Education (US)";
        const string DISTRIBUTOR = "Pearson";
        const int NUM_OF_PAGES = 464;

        static void Main(string[] args)
        {
            EBook eBookBuilder = new EBook();
            Book ebook = eBookBuilder.SetFormat()
                                   .SetName(BOOK_NAME)
                                   .SetAuthor(AUTHOR)
                                   .SetPublisher(PUBLISHER)
                                   .SetLanguage("English")
                                   .SetNumOfPages(NUM_OF_PAGES)
                                   .Build();


            Console.WriteLine(ebook);
            Console.WriteLine("\n---------------------------------------------\n");

            PrintedBook pBookBuilder = new PrintedBook();
            Book pbook = pBookBuilder.SetFormat()
                                   .SetName(BOOK_NAME)
                                   .SetAuthor(AUTHOR)
                                   .SetPublisher(PUBLISHER)
                                   .SetDistributor(DISTRIBUTOR)
                                   .SetNumOfPages(NUM_OF_PAGES)
                                   .Build();

            Console.WriteLine(pbook);

        }
    }
				
			

כך נראה הפלט – באחד הפורמט דיגיטלי ובשני מודפס:

חדי העין ישימו לב שבאחד מהם לא כתובה השפה אנגלית וזה בדיוק עונה לנו על הצורך שלא תמיד נרצה לבנות הכל וחלק מהשדות הן לגמרי אופציונאליים.

הדוגמא זמינה גם בGitHub

סיכום

תבנית Buider היא פתרון מעולה במקרים שיש לך אובייקט עם קונסטרוקטור מרובה פרמטרים וכאשר חלק מהפרמטרים הם אובייקטים בעצמם.

קונסטרוקטור בעל פרמטרים אופציונליים מרובים הוא גם סיבה מצויינת להשתמש בתבנית הבנאי.

אמיר שטיימן
WRITEN BY

אמיר שטיימן

Backend Engineer @Cynet
ביום יום מפתח Backend בסביבת SaaS, מיקרו סרביסים בשילוב של מערכות מבוזרות. בזמני הפנוי - לומד ומשתדרג בעולם התוכנה, אוהד מכבי חיפה, פלייסטיישן ובירה עם חברים :)
Linkedin | Twitter

One thought on “מסבירים את תבנית הבנאי (Builder Pattern)

  1. היי עמית,
    דבר ראשון, תודה רבה! אשמח לעוד הסברים על סוגי התבניות השונים.
    בהקשר להסבר הזה , עקבתי אחרי ההסבר וכתבתי אחד בעצמי, כשאני מנסה להגדיר בתוך המחלקה את האובייקט הפנימי (לדוגמתך Book _book = new Book();) הוא זורק שגיאה שהוא מצפה ממני להכניס פרמטרים..ואז אנחנו מאבדים את כל היתרונות של התבנית הזו, מכיר?
    תודה רבה.

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *