Паттерн построителя в C#

Первоначально было опубликовано здесь

Конструктор — это шаблон проектирования, который позволяет нам
создавать сложные объекты шаг за шагом. Этот паттерн отделяет объект от его представления, так что один и тот же процесс построения может создавать различные представления.

Конструктор — это удобный инструмент, когда у нас есть коллекция частей для сложного объекта и нам нужно собрать их все вместе. С помощью этого паттерна мы можем инициализировать каждую часть сложного объекта шаг за шагом, а затем собрать их все вместе до полного объекта.

Вы можете найти код примера в этом посте на Github

Концептуализация проблемы

Представьте себе сложный объект, который требует пошаговой инициализации множества полей, некоторые из которых являются необязательными. Такой код инициализации обычно либо погребен под чудовищным конструктором с большим количеством параметров, либо разбросан по многочисленным конструкторам, которые инициализируют только некоторые поля.

Давайте подумаем, как создать объект Pizza. Чтобы создать простую пиццу, нам нужно добавить простое тесто, немного томатного соуса и измельченный сыр. Но что, если мы хотим сделать большую, лучшую пиццу, со сливочным сыром в корже и другими вкусностями (например, пепперони, болгарским перцем и сыром моцарелла)?

Самое простое решение — расширить базовый класс Pizza и создать множество подклассов, чтобы охватить все комбинации параметров. Но в конечном итоге вы получите значительное количество подклассов. Любой новый параметр потребует еще большего роста этой иерархии.

Есть и другой подход, который не требует создания дополнительных подклассов. Мы можем создать гигантский конструктор прямо в базовом классе Pizza со всеми возможными параметрами, которые управляют объектом пиццы. Хотя такой подход действительно устраняет необходимость в многочисленных подклассах, он создает другую проблему.

В большинстве случаев большинство параметров будут неиспользуемыми, что сделает вызов конструктора довольно уродливым. Например, только часть пицц имеет ананас в качестве начинки, поэтому параметры, связанные с ним, будут бесполезны большую часть времени.

Паттерн Builder предлагает извлечь код построения объекта из этого собственного класса и перенести его в отдельный объект под названием builder.

Паттерн организует построение объекта в отдельный набор шагов (AddPepperoni, AddBellPeppers и т.д.). Чтобы создать новый объект, нам достаточно вызвать только те шаги, которые необходимы для создания определенной конфигурации объекта.

Директор

Мы можем пойти дальше и выделить серию вызовов шагов конструктора, используемых для создания продукта, в отдельный класс, называемый директором. Класс director определяет порядок выполнения шагов построения, в то время как builder обеспечивает реализацию этих шагов.

Наличие директора необязательно. Мы всегда можем вызвать шаги построения в определенном порядке непосредственно из клиентского кода. Однако класс director может быть полезен для хранения различных процедур построения, чтобы их можно было повторно использовать во всем коде.

Более того, класс Director скрывает детали построения продукта от клиентского кода. Код должен только связать построитель с директором, запустить построение и получить результаты от построителя.

Структурирование шаблона Builder

На следующей диаграмме представлены различные части конструктора.

Шаблон состоит из следующих частей:

  • Директор: Директор — это класс, который решает, на каких этапах должен быть построен сложный класс. В данном примере имя сложного объекта — Product. Он не строит класс Product напрямую, а вызывает интерфейс IBuilder для создания частей.
  • IBuilder: Интерфейс IBuilder определяет все необходимые методы для создания класса Product. Эти методы являются общими для всех реализаций Builder. Директор вызывает метод Build для получения результата построения.
  • ConcreteBuilder: Реализует интерфейс IBuilder. Он берет класс Product и инкрементально строит его.
  • Product: Объект Product — это сложный класс, который нам нужно построить.

Чтобы продемонстрировать, как работает паттерн Builder, мы обратим наши голодные глаза к одному из лучших блюд для обеда: великолепной пицце.

Вот что касается пиццы: единственное, что определяет пиццу, — это что-то съедобное поверх какого-то приготовленного теста.

При этом различные виды пиццы требуют других этапов приготовления, но все равно это всего лишь пицца. В большинстве случаев одни и те же ингредиенты можно использовать для приготовления разных видов пиццы. Давайте посмотрим, как мы можем использовать паттерн Builder для создания вкусных пицц.

Для начала мы реализуем участника Director. Мы назовем наш класс Director AssemblyLine, сделаем его классом, и он будет определять, на каких этапах происходит процесс создания пиццы.

/// 
/// The Director class
/// 
public class AssemblyLine
{
    // Builder uses a complex series of steps
    public void Assemble(PizzaBuilder pizzaBuilder)
    {
        pizzaBuilder.AddDough();
        pizzaBuilder.AddSauce();
        pizzaBuilder.AddCheeses();
        pizzaBuilder.AddMeats();
        pizzaBuilder.AddVeggies();
        pizzaBuilder.AddExtras();
     }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Нам также необходимо определить участника Product, который строится участником Builder. В нашем примере Продукт — это, конечно же, Pizza.

/// 
/// The Product class
/// 
public class Pizza {
  private string pizzaName;
  private Dictionary ingredients =
    new Dictionary();

  public Pizza(string pizzaName) {
    this.pizzaName = pizzaName;
  }

  // Indexer
  public string this[string key] {
    get {
      return ingredients[key];
    }
    set {
      ingredients[key] = value;
    }
  }

  public void Display() {
    Console.WriteLine("n----------------------------");
    Console.WriteLine($"Pizza: {pizzaName}");

    Console.WriteLine($" Dough: {ingredients["
      dough "]}");
    Console.WriteLine($" Sauce: {ingredients["
      sauce "]}");
    Console.WriteLine($" Meats: {ingredients["
      meats "]}");
    Console.WriteLine($" Cheeses: {ingredients["
      cheeses "]}");
    Console.WriteLine($" Veggies: {ingredients["
      veggies "]}");
    Console.WriteLine($" Extras: {ingredients["
      extras "]}");
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь, когда мы знаем определение продукта, который мы создаем, давайте создадим участника Builder. Это будет абстрактный класс с именем PizzaBuilder.

/// 
/// The Builder Abstract class
/// 
public abstract class PizzaBuilder
{
    protected Pizza pizza;

    // Get the pizza instance
    public Pizza Pizza
    {
        get { return pizza; }
    }

    public abstract void AddDough();
    public abstract void AddSauce();
    public abstract void AddMeats();
    public abstract void AddCheeses();
    public abstract void AddVeggies();
    public abstract void AddExtras();
}
Вход в полноэкранный режим Выход из полноэкранного режима

Каждый подкласс PizzaBuilder должен будет реализовать все абстрактные методы, чтобы правильно построить пиццу.

Далее, давайте реализуем несколько классов ConcreteBuilder для создания
конкретных пицц.

/// 
/// A ConcreteBuilder class
/// 
class MeatFeastHot : PizzaBuilder
{
    public MeatFeastHot()
    {
        pizza = new Pizza("Meat Feast Hot");
    }

    public override void AddDough()
    {
        pizza["dough"] = "Wheat pizza dough";
    }

    public override void AddSauce()
    {
        pizza["sauce"] = "Tomato base";
    }

    public override void AddMeats()
    {
        pizza["meats"] = "Pepperoni, Ham, Beef, Chicken";
    }

    public override void AddCheeses()
    {
        pizza["cheeses"] = "Signature triple cheese blend, mozzarella";
    }

    public override void AddVeggies()
    {
        pizza["veggies"] = "";
    }

    public override void AddExtras()
    {
        pizza["extras"] = "jalapenos";
    }
}

/// 
/// A ConcreteBuilder class
/// 
class HotNSpicyVeg : PizzaBuilder
{
    public HotNSpicyVeg()
    {
        pizza = new Pizza("Hot 'N' Spicy Veg");
    }

    public override void AddDough()
    {
        pizza["dough"] = "12-grain pizza dough";
    }

    public override void AddSauce()
    {
        pizza["sauce"] = "Tomato base";
    }

    public override void AddMeats()
    {
        pizza["meats"] = "";
    }

    public override void AddCheeses()
    {
        pizza["cheeses"] = "Signature triple cheese blend, mozzarella";
    }

    public override void AddVeggies()
    {
        pizza["veggies"] = "Mushrooms, Peppers, Red Onions";
    }

    public override void AddExtras()
    {
        pizza["extras"] =  "Jalapenos";
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Как только мы подготовим все наши классы ConcreteBuilder, мы можем использовать их в методе Main() следующим образом:

static void Main(string[] args)
{
    PizzaBuilder builder;

    // Create a pizza assembly line
    AssemblyLine shop = new AssemblyLine();

    // Construct and display pizzas
    builder = new MeatFeastHot();
    shop.Assemble(builder);
    builder.Pizza.Display();

    builder = new HotNSpicyVeg();
    shop.Assemble(builder);
    builder.Pizza.Display();

    // Wait for user
    Console.ReadKey();
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Приятной особенностью этого паттерна является то, что теперь мы можем повторно использовать класс AssemblyLine в любом PizzaBuilder, который мы захотим, и у нас есть более тонкий контроль над тем, как строится пицца.

Плюсы и минусы шаблона Builder

✔ Мы можем конструировать объекты пошагово, откладывать шаги конструирования или даже выполнять шаги рекурсивно. ❌ Общая сложность кода возрастает, поскольку паттерн требует создания нескольких новых классов.
✔ Мы можем повторно использовать один и тот же код построения при создании различных представлений продуктов.
✔ Мы можем изолировать сложный код построения от бизнес-логики продукта, удовлетворяя тем самым принципу единой ответственности.

Связь с другими паттернами

Существует несколько паттернов, связанных с Конструктором.

  • Многие конструкции начинаются как фабричные методы (менее сложные, но более настраиваемые, чем подклассификация) и эволюционируют к абстрактным фабрикам, прототипам или конструкторам (более гибким, чем фабричные методы, но более сложным).
  • Мы можем использовать Builders при создании сложных композитных деревьев.
  • Мы можем объединить паттерн Builder с паттерном Bridge. Директор будет играть роль абстракции, а Builders будут выступать в качестве реализаций.
  • Строители часто реализуются как синглтоны

Варианты паттерна Builder

Fluent Builder

Расширение паттерна Builder с помощью Fluent API делает его более читабельным. Это также позволяет нам составлять цепочки утверждений для конфигурации объекта. Таким образом, больше нет необходимости указывать объект builder в каждом операторе.

public class FluentPizzaBuilder {
    private readonly Pizza pizza;

    public FluentPizzaBuilder(string pizzaName) {
        pizza = new Pizza(pizzaName);
    }

    public FluentPizzaBuilder WithDough(string dough) {
        pizza["dough"] = dough;
        return this;
    }

    public FluentPizzaBuilder WithSauce(string sauce) {
        pizza["sauce"] = sauce;
        return this;
    }

    public FluentPizzaBuilder WithMeat(string meat) {
        pizza["meats"] = meat;
        return this;
    }

    public FluentPizzaBuilder WithCheese(string cheese) {
        pizza["cheeses"] = cheese;
        return this;
    }

    public FluentPizzaBuilder WithVeggie(string veggie) {
        pizza["veggies"] = veggie;
        return this;
    }

    public FluentPizzaBuilder WithExtra(string extra) {
        pizza["extras"] = extra;
        return this;
    }

    public Pizza Build() {
        return pizza;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем использовать Fluent Builder для создания новой пиццы.

var pizzaBuilder = new FluentPizzaBuilder("Supreme");

var pizzaSupreme = pizzaBuilder
    .WithDough("12-grain pizza dough")
    .WithSauce("Tomato base")
    .WithMeat("Pepperoni, Seasoned Minced Beef, Spicy Pork Sausage")
    .WithVeggie("Mushroom, Mixed Peppers, Red Onions")
    .WithCheese("Mozzarella")
    .WithExtra("Jalapenos")
    .Build();

pizzaSupreme.Display();
Вход в полноэкранный режим Выход из полноэкранного режима

Конструктор родитель-ребенок

Мы можем использовать отношения «родитель-ребенок» с шаблоном Builder для
создания сложных объектов. Сначала мы определим родительский класс, которому поручено создать сложный объект, а затем один или несколько дочерних классов, создающих части объекта.

Нам нужно определить некоторые продукты, которые будут созданы дочерними классами. Для данного примера у нас есть 3 продукта, Гарнир, Салат и Сделка, которая будет содержать несколько Гарниров и Салат.

public class Side
{
    public string Item { get; set; }
    public string Dip { get; set; }
    public string Size { get; set; }
}
Вход в полноэкранный режим Выход из полноэкранного режима
public class Salad
{
    public string Base{get;set;}
    public string Veggies{get;set;}
    public string Meats{get;set;}
    public string Cheeses{get;set;}
    public string Dressing{get;set;}
}
Войти в полноэкранный режим Выйти из полноэкранного режима
public class Deal {
    public List < Side > Sides {
        get;
        set;
    }
    public Salad Salad {
        get;
        set;
    }

    public void Display() {
        foreach(var side in Sides) {
            Console.WriteLine($"Side: {side.Item}");
            Console.WriteLine($"Dip: {side.Dip}");
            Console.WriteLine($"Size: {side.Size}");
            Console.WriteLine();
        }

        Console.WriteLine("Salad:");
        Console.WriteLine($"Base: { Salad.Base}");
        Console.WriteLine($"Veggies: {Salad.Veggies}");
        Console.WriteLine($"Meats: {Salad.Meats}");
        Console.WriteLine($"Cheeses: {Salad.Cheeses}");
        Console.WriteLine($"Dressing: {Salad.Dressing}");
    }
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем нам нужно будет создать Builder для каждого из наших продуктов. Обратите внимание, что наш DealBuilder действует как родительский Builder и вызывает SaladBuilder и SideBuilder, которые действуют как дочерние Builder.

public class DealBuilder
{
    private readonly Deal deal = new Deal();

    public DealBuilder()
    {
        deal.Sides = new List();
    }

    public SaladBuilder AddSalad()
    {
        return new SaladBuilder(this, deal);
    }

    public SideBuilder AddSide()
    {
        return new SideBuilder(this, deal);
    }

    public Deal Build()
    {
        return deal;
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
public class SaladBuilder
{
    private readonly DealBuilder _dealBuilder;
    private readonly Deal _deal;
    private readonly Salad _salad = new Salad();

    public SaladBuilder(DealBuilder parentBuilder, Deal deal)
    {
        _dealBuilder = parentBuilder;
        _deal = deal;
    }

    public SaladBuilder WithBase(string saladBase)
    {
        _salad.Base = saladBase;
        return this;
    }

    public SaladBuilder WithVeggies(string veggies)
    {
        _salad.Veggies = veggies;
        return this;
    }

    public SaladBuilder WithMeats(string meats)
    {
        _salad.Meats = meats;
        return this;
    }

    public SaladBuilder WithCheeses(string cheeses)
    {
        _salad.Cheeses = cheeses;
        return this;
    }

    public SaladBuilder WithDressing(string dressing)
    {
        _salad.Dressing = dressing;
        return this;
    }

    public DealBuilder BuildSalad()
    {
        _deal.Salad = _salad;
        return _dealBuilder;
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима
public class SideBuilder
{
    private DealBuilder dealBuilder;
    private Deal deal;
    private Side side = new Side();

    public SideBuilder(DealBuilder dealBuilder, Deal deal)
    {
        this.dealBuilder = dealBuilder;
        this.deal = deal;
    }

    public SideBuilder WithItem(string item)
    {
        side.Item = item;
        return this;
    }

    public SideBuilder WithDip(string dip)
    {
        side.Dip = dip;
        return this;
    }

    public SideBuilder WithSize(string size)
    {
        side.Size = size;
        return this;
    }

    public DealBuilder BuildSide()
    {
        deal.Sides.Add(side);
        return dealBuilder;
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Теперь мы можем использовать наши Builders для создания объекта Deal:

var deal = new DealBuilder()
    .AddSide()
        .WithItem("Spicy Loaded Pepperoni Wedges")
        .WithDip("Mayo")
        .WithSize("Large")
        .BuildSide()
    .AddSide()
        .WithItem("Spicy Cheesy Pepperoni Garlic Bread")
        .WithDip("BBQ Sauce")
        .WithSize("Large")
        .BuildSide()
    .AddSalad()
        .WithBase("Lettuce")
        .WithVeggies("Cherry Tomatoes, Red Cabbage, Carrot")
        .WithDressing("Garlic & Herbs")
        .BuildSalad()
    .Build();
Войти в полноэкранный режим Выйти из полноэкранного режима

В результате будет создан следующий сложный объект:

Прогрессивный построитель

Прогрессивный построитель — это вариант шаблона Builder. Он последовательно использует несколько построителей для определения фиксированной последовательности вызовов методов. Преимущество такой реализации заключается в том, что она гарантирует, что объект будет построен в правильном порядке.

В приведенном ниже примере мы создадим Pizza Deal, в котором будет несколько гарниров, салат, пицца, несколько напитков и десерт.

Как обычно, сначала мы определим классы продуктов

public class Side {
    public string Item {
        get;
        set;
    }
    public string Dip {
        get;
        set;
    }
    public string Size {
        get;
        set;
    }

    public void Display() {
        Console.WriteLine("n--------- Side Dish ---------");
        Console.WriteLine($"Item: {Item}");
        Console.WriteLine($"Dip: {Dip}");
        Console.WriteLine($"Size: {Size}");
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
public class Salad {
    public string Base {
        get;
        set;
    }
    public string Veggies {
        get;
        set;
    }
    public string Meats {
        get;
        set;
    }
    public string Cheeses {
        get;
        set;
    }
    public string Dressing {
        get;
        set;
    }

    public void Display() {
        Console.WriteLine("n--------- Salad Dish ---------");
        Console.WriteLine($"Base: {Base}");
        Console.WriteLine($"Veggies: {Veggies}");
        Console.WriteLine($"Meats: {Meats}");
        Console.WriteLine($"Cheeses: {Cheeses}");
        Console.WriteLine($"Dressing: {Dressing}");
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима
public class Salad {
    public string Base {
        get;
        set;
    }
    public string Veggies {
        get;
        set;
    }
    public string Meats {
        get;
        set;
    }
    public string Cheeses {
        get;
        set;
    }
    public string Dressing {
        get;
        set;
    }

    public void Display() {
        Console.WriteLine("n--------- Salad Dish ---------");
        Console.WriteLine($"Base: {Base}");
        Console.WriteLine($"Veggies: {Veggies}");
        Console.WriteLine($"Meats: {Meats}");
        Console.WriteLine($"Cheeses: {Cheeses}");
        Console.WriteLine($"Dressing: {Dressing}");
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима
 public class Drink {
     public string Item {
         get;
         set;
     }
     public string Size {
         get;
         set;
     }

     public void Display() {
         Console.WriteLine("n--------- Drink Dish ---------");
         Console.WriteLine($"Item: {Item}");
         Console.WriteLine($"Size: {Size}");
     }
 }
Войти в полноэкранный режим Выход из полноэкранного режима
 public class Dessert {
     public string Item {
         get;
         set;
     }
     public string Size {
         get;
         set;
     }

     public void Display() {
         Console.WriteLine("n--------- Dessert Dish ---------");
         Console.WriteLine($"Item: {Item}");
         Console.WriteLine($"Size: {Size}");
     }
 }
Войти в полноэкранный режим Выход из полноэкранного режима

И, наконец, объект «Сделка

 public class MenuDeal {
     public List < Side > Sides {
         get;
         set;
     }
     public Salad Salad {
         get;
         set;
     }
     public Pizza Pizza {
         get;
         set;
     }
     public Drink Drink {
         get;
         set;
     }
     public Dessert Dessert {
         get;
         set;
     }

     public MenuDeal() {
         Sides = new List < Side > ();
     }

     public void Display() {
         foreach(var side in Sides)
         side.Display();
         Salad.Display();
         Pizza.Display();
         Drink.Display();
         Dessert.Display();
     }
 }
Войти в полноэкранный режим Выход из полноэкранного режима

Далее мы определим наши объекты Builder. Обратите внимание, что каждый Builder может вызывать следующий Builder в цепочке.

public class DessertBuilder {
    private MenuDealBuilder menuDealBuilder;
    private MenuDeal menuDeal;

    private Dessert dessert;

    public DessertBuilder(MenuDealBuilder menuDealBuilder, MenuDeal menuDeal) {
        this.menuDealBuilder = menuDealBuilder;
        this.menuDeal = menuDeal;

        dessert = new Dessert();
    }

    public DessertBuilder WithItem(string item) {
        dessert.Item = item;
        return this;
    }

    public DessertBuilder WithSize(string size) {
        dessert.Size = size;
        return this;
    }

    public MenuDeal Build() {
        menuDeal.Dessert = dessert;
        return menuDeal;
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима
public class SideDishBuilder {
    private MenuDealBuilder menuDealBuilder;
    private MenuDeal menuDeal;

    private Side sideDish;

    public SideDishBuilder(MenuDealBuilder menuDealBuilder, MenuDeal menuDeal) {
        this.menuDealBuilder = menuDealBuilder;
        this.menuDeal = menuDeal;
        sideDish = new Side();
    }

    public SideDishBuilder WithItem(string item) {
        sideDish.Item = item;
        return this;
    }

    public SideDishBuilder WithDip(string dip) {
        sideDish.Dip = dip;
        return this;
    }

    public SideDishBuilder WithSize(string size) {
        sideDish.Size = size;
        return this;
    }

    public SideDishBuilder AddSideDish() {
        menuDeal.Sides.Add(sideDish);
        return new SideDishBuilder(menuDealBuilder, menuDeal);
    }

    public SaladBuilder AddSaladDish() {
        menuDeal.Sides.Add(sideDish);
        return new SaladBuilder(menuDealBuilder, menuDeal);
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
using ProgressiveBuilder.Products;

namespace ProgressiveBuilder.Builders {
    public class SaladBuilder {
        private MenuDealBuilder menuDealBuilder;
        private MenuDeal menuDeal;

        private Salad salad;

        public SaladBuilder(MenuDealBuilder menuDealBuilder, MenuDeal menuDeal) {
            this.menuDealBuilder = menuDealBuilder;
            this.menuDeal = menuDeal;

            salad = new Salad();
        }

        public SaladBuilder WithBase(string saladBase) {
            salad.Base = saladBase;
            return this;
        }

        public SaladBuilder WithVeggies(string veggies) {
            salad.Veggies = veggies;
            return this;
        }

        public SaladBuilder WithMeats(string meats) {
            salad.Meats = meats;
            return this;
        }

        public SaladBuilder WithCheeses(string cheeses) {
            salad.Cheeses = cheeses;
            return this;
        }

        public SaladBuilder WithDressing(string dressing) {
            salad.Dressing = dressing;
            return this;
        }

        public PizzaBuilder AddPizzaDish() {
            menuDeal.Salad = salad;
            return new PizzaBuilder(menuDealBuilder, menuDeal);
        }
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима
public class PizzaBuilder
    {
        private MenuDealBuilder menuDealBuilder;
        private MenuDeal menuDeal;

        private Pizza pizza;

        public PizzaBuilder(MenuDealBuilder menuDealBuilder, MenuDeal menuDeal)
        {
            this.menuDealBuilder = menuDealBuilder;
            this.menuDeal = menuDeal;

            pizza = new Pizza();
        }

        public PizzaBuilder WithDough(string dough)
        {
            pizza.Dough = dough;
            return this;
        }

        public PizzaBuilder WithSauce(string sauce)
        {
            pizza.Sauce = sauce;
            return this;
        }

        public PizzaBuilder WithCheeses(string cheeses)
        {
            pizza.Cheeses = cheeses;
            return this;
        }

        public PizzaBuilder WithMeats(string meats)
        {
            pizza.Meats = meats;
            return this;
        }

        public PizzaBuilder WithVeggies(string veggies)
        {
            pizza.Veggies = veggies;
            return this;
        }

        public PizzaBuilder WithExtras(string extras)
        {
            pizza.Extras = extras;
            return this;
        }

        public DrinkBuilder AddDrink()
        {
            menuDeal.Pizza = pizza;
            return new DrinkBuilder(menuDealBuilder, menuDeal);
        }
    }
Войти в полноэкранный режим Выход из полноэкранного режима
public class DrinkBuilder {
    private MenuDealBuilder dealBuilder;
    private MenuDeal menuDeal;

    private Drink drink;

    public DrinkBuilder(MenuDealBuilder dealBuilder, MenuDeal menuDeal) {
        this.dealBuilder = dealBuilder;
        this.menuDeal = menuDeal;

        drink = new Drink();
    }

    public DrinkBuilder WithItem(string item) {
        drink.Item = item;
        return this;
    }

    public DrinkBuilder WithSize(string size) {
        drink.Size = size;
        return this;
    }

    public DessertBuilder AddDesert() {
        menuDeal.Drink = drink;
        return new DessertBuilder(dealBuilder, menuDeal);
    }
}
Вход в полноэкранный режим Выход из полноэкранного режима
public class DessertBuilder {
    private MenuDealBuilder menuDealBuilder;
    private MenuDeal menuDeal;

    private Dessert dessert;

    public DessertBuilder(MenuDealBuilder menuDealBuilder, MenuDeal menuDeal) {
        this.menuDealBuilder = menuDealBuilder;
        this.menuDeal = menuDeal;

        dessert = new Dessert();
    }

    public DessertBuilder WithItem(string item) {
        dessert.Item = item;
        return this;
    }

    public DessertBuilder WithSize(string size) {
        dessert.Size = size;
        return this;
    }

    public MenuDeal Build() {
        menuDeal.Dessert = dessert;
        return menuDeal;
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Последним шагом будет вызов цепочки построителей для создания объекта сделки:

var menuBuilder = new MenuDealBuilder();
var menu = menuBuilder
    .AddSideDish()
        .WithItem("Breaded All-White Chicken Breast, Baked in Stone Oven")
        .WithDip("Frank's Spicy Buffalo")
        .WithSize("Large")
    .AddSideDish()
        .WithItem("Gluten Free Corn Tortilla Chips")
        .WithDip("Guacamole")
        .WithSize("Large")
    .AddSaladDish()
        .WithBase("Fresh Mixed Lettuce")
        .WithVeggies("Chopped Onions, Green Bell Peppers, Black Olives, Mushrooms")
        .WithMeats("Fajita Chicken, Bacon Bits")
        .WithCheeses("Mozzarella")
        .WithDressing("Zesty Italian")
    .AddPizzaDish()
        .WithDough("Sourdough with Cream Cheese Crust")
        .WithSauce("Tomato Sauce")
        .WithMeats("Beef, Sausage, Pepperoni")
        .WithVeggies("Mushrooms, Green Bell Peppers, Onions")
        .WithCheeses("Mozzarella")
    .AddDrink()
        .WithItem("Soda")
        .WithSize("Large")
    .AddDesert()
        .WithItem("Cookie Dough Ice Cream")
        .WithSize("Large")
    .Build();

menu.Display();
Войти в полноэкранный режим Выйти из полноэкранного режима

Каждый строитель создаст свою часть объекта «Сделка», а затем вызовет следующего строителя в цепочке. Это гарантирует, что части будут созданы в правильном порядке.

Вышеописанное позволит создать этот сложный объект:

Заключительные размышления

В этой статье мы обсудили, что такое паттерн Builder, когда его использовать, а также плюсы и минусы использования этого паттерна проектирования. Затем мы обсудили, как паттерн Builder соотносится с другими классическими паттернами проектирования. Наконец, мы рассмотрели некоторые вариации паттерна Builder, которые либо улучшают читабельность паттерна (FluentBuilder), либо решают конкретную проблему.

Стоит отметить, что паттерн Builder, как и остальные паттерны проектирования, представленные Gang of Four, не является панацеей или универсальным решением при проектировании приложения. И снова инженеры должны сами решать, когда использовать тот или иной паттерн. В конце концов, эти шаблоны полезны, когда используются как точный инструмент, а не как кувалда.

Оцените статью
devanswers.ru
Добавить комментарий