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