Как стать автором
Обновить

Паттерн Builder (Строитель)

Уровень сложностиПростой

В этой статье мы рассмотрим такой паттерн как Builder, с примерами на С#
В книге "Паттерны Объектно-Ориентированного Программирование" Банды четверных написано следующее:

Отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирование могут получаться разные представления

Чаще всего такие объяснения усложняют понимание всей сути паттерна поэтому я предлагаю упросить это определение:

Предоставляет класс цель которого это настройка и создание другого экземпляра класса

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

Представим ситуацию:

Вы - Продавец бургеров, и у вас есть программа которая позволяет создавать самостоятельно бургер с разной начинкой, для того чтобы его заказать, например в одном бургере есть котлетка, кетчуп, огурцы и помидоры, в другом есть только котлетка, помидоры и лист салата которого нету в первом
Как бы могло выглядеть решение? Создать конструктор который позволит нам указать какие продукты есть в нашем бургере

using System;

namespace Patterns.Builder
{
   public class Burger
   {
       internal Burger(bool hasPatty, bool hasLettuce, bool hasTomatoes, 
                  bool hasCucumbers, bool hasOnions, bool hasCheese, bool hasKetchup)
       {
           //Логика Конструктора
       }
  }

Наш конструктор получается слишком громоздким и плохо читаемым поэтому создание экземпляра класса будет плохо читаемым и неудобным, для решение такой проблемы нам и приходить на помощь паттерн Builder. С помощью него мы делегируем создание экземпляров другому классу. Давайте создадим интерфейс для такого класса и сам класс

using System;

namespace Patterns.Builder
{
     public interface IBurgerBuilder
     {
          IBurgerBuilder AddPatty(); //Для чего нам возвращать интерфейс расскажу ниже
          IBurgerBuilder AddLettuce();
          IBurgerBuilder AddTomatoes();
          IBurgerBuilder AddCucumbers();
          IBurgerBuilder AddOnions();
          IBurgerBuilder AddCheese();
          IBurgerBuilder AddKetchup();
          Burger Build();
     }
}
using System;

namespace Patterns.Builder
{
    public class BurgerBuilder : IBurgerBuilder
    {
        //Присваивать false нету смысла так как при обьявлении он будет присвоен по умолчанию
        private bool _hasPatty;
        private bool _hasLettuce;
        private bool _hasTomatoes;
        private bool _hasCucumbers;
        private bool _hasOnions;
        private bool _hasCheese;
        private bool _hasKetchup;

        public IBurgerBuilder AddPatty()
        {
            _hasPatty = true;
            return this; // Возвращаем интерфейс
        }
        public IBurgerBuilder AddLettuce()
        {
            _hasLettuce = true;
            return this; 
        }

        public IBurgerBuilder AddTomatoes() 
        {
            _hasTomatoes = true;
            return this; 
        }

        public IBurgerBuilder AddCucumbers()
        {
            _hasCucumbers = true;
            return this; 
        }

        public IBurgerBuilder AddOnions()
        {
            _hasOnions = true;
            return this; 
        }

        public IBurgerBuilder AddCheese()
        {
            _hasCheese = true;
            return this; 
        }

        public IBurgerBuilder AddKetchup()
        {
            _hasKetchup = true;
            return this; 
        }

        public Burger Build()
        {
            return new Burger(_hasPatty, _hasLettuce, _hasTomatoes,
                              _hasCucumbers, _hasOnions, _hasCheese,
                              _hasKetchup);
        }
    }

}

Также я предлагаю реализовать наш класс Burger до конца

using System;
using System.Collections.Generic;

namespace Patterns.Builder
{
    public class Burger
    {
        //Состав Бургера
        private bool _hasPatty;
        private bool _hasLettuce;
        private bool _hasTomatoes;
        private bool _hasCucumbers;
        private bool _hasOnions;
        private bool _hasCheese;
        private bool _hasKetchup;

        //Конструктор класса для создание Бургеров
        internal Burger(bool hasPatty, bool hasLettuce, bool hasTomatoes,
                      bool hasCucumbers, bool hasOnions, bool hasCheese,
                      bool hasKetchup)
        {
            _hasPatty = hasPatty;
            _hasLettuce = hasLettuce;
            _hasTomatoes = hasTomatoes;
            _hasCucumbers = hasCucumbers;
            _hasOnions = hasOnions;
            _hasCheese = hasCheese;
            _hasKetchup = hasKetchup;
        }

    }
}

Теперь я предлагаю посмотреть и сравнить создание экземпляра класса без паттерна и с ним(при условии что конструктор имеет другой модификатор доступа)

var myBurger = new Burger(true, false, true, true, true, true, false);

Без использование паттерна создание экземпляра понять код очень тяжело, с использованием паттерна Builder это будет выглядеть так:

var builder = new Builder();
var myBurger = builder
       .AddPatty()
       .AddTomatoes()
       .AddCucumbers()
       .AddCheese()
       .Build();

Это был один из примеров реализации паттерна Builder который называется FluentBuilder Для его реализации мы используем цепочки методов ради которых мы и возвращали интерфейс IBurgerBuilder
Один из реальных примеров использование такого паттерна является настройка конфигурации логирование в Serilog (Configuration Basics · serilog/serilog Wiki )

Log.Logger = new LoggerConfiguration()
      .MinimumLevel.Debug()
      .WriteTo.Console()
      .CreateLogger();

Примером реализации классического класса Builder является класс WebApplicationBuilder из Asp.Net Core ( WebApplication и WebApplicationBuilder в минимальных приложениях API | Microsoft Learn )

using Microsoft.AspNetCore.HttpLogging;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpLogging(opts =>
  opts.LoggingFields = HttpLoggingFields.RequestProperties
);

builder.Logging.AddFilter(
 "Microsoft.AspNetCore.HttpLogging", LogLevel.Debug
);

var app = builder.Build();
app.MapGet("/", () => "I knew the new pattern!!");
app.Run();

Также иногда при реализации этого паттерна используется Derector в этой статье мы его не затрагиваем но вы можете прочитать о нем тут c# - Builder Pattern: What is the purpose of the Director? - Stack Overflow

Теги:
Хабы:
Данная статья не подлежит комментированию, поскольку её автор ещё не является полноправным участником сообщества. Вы сможете связаться с автором только после того, как он получит приглашение от кого-либо из участников сообщества. До этого момента его username будет скрыт псевдонимом.