Search
Write a publication
Pull to refresh

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

Level of difficultyEasy

В этой статье мы рассмотрим такой паттерн как 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

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.