Creational

Builder (Будівельник)

Builder (Будівельник)

Вступ та Контекст

Уявіть, що ви розробляєте систему для створення HTTP запитів. Простий GET запит може виглядати так:

var request = new HttpRequest("https://api.example.com/users");

Але що, якщо вам потрібен складніший запит з заголовками, параметрами запиту, тілом, таймаутом, автентифікацією?

// ❌ ПРОБЛЕМА: Телескопічний конструктор (Telescoping Constructor)
var request = new HttpRequest(
    "https://api.exception.com/users",
    "POST",
    new Dictionary<string, string> { ["Authorization"] = "Bearer token" },
    new Dictionary<string, string> { ["userId"] = "123" },
    "{\"name\": \"John\"}",
    TimeSpan.FromSeconds(30),
    true
);

// Незрозуміло, що означає кожен параметр!
// А якщо деякі параметри опціональні?

Builder (Будівельник) — це породжуючий патерн проектування, який дозволяє створювати складні об'єкти крок за кроком, надаючи зрозумілий та гнучкий інтерфейс для конфігурації.

Builder вирішує три ключові проблеми:
  1. Телескопічні конструктори з десятками параметрів
  2. Незрозумілі виклики з багатьма null та default значеннями
  3. Обов'язкова послідовність створення складних об'єктів ::

Коли використовувати

СценарійОбґрунтування
Складні об'єктиКлас з більш ніж 3-4 параметрами конструктора
Багато опціональних параметрівУникнення конструкторів з десятками null
Крокова конструкціяОб'єкт створюється поетапно (наприклад, SQL запит)
Різні представленняОдин процес створення, різні результати (Car vs CarManual)
Fluent InterfaceПотрібен зручний та читабельний API (method chaining)

Фундаментальні Концепції

Академічне визначення

Builder (Будівельник) — це породжуючий патерн проектування, який дозволяє створювати складні об'єкти покроково. Патерн дозволяє використовувати один і той самий код конструювання для отримання різних типів та представлень об'єктів.

Ключова ідея

Проблема складних конструкторів виникає, коли в класі забагато параметрів. Традиційні підходи:
  1. Телескопічні конструктори — створення множини перевантажених конструкторів
  2. Великий конструктор — один конструктор з десятками параметрів
  3. Сеттери — створення об'єкта з частково ініціалізованим станом
Builder вирішує це через:
  • Винесення логіки створення в окремий клас (Builder)
  • Покрокове налаштування об'єкта через методи Builder
  • Фінальний метод Build(), який повертає готовий об'єкт
Чому Builder краще за сеттери?
Сеттери дозволяють створити об'єкт у несумісному стані (частково ініціалізований). Builder гарантує, що об'єкт буде повністю валідним після виклику Build(). Крім того, Builder дозволяє створювати незмінні об'єкти (immutable), тоді як сеттери вимагають змінюваних властивостей.

Аналогія з реального світу

Будівництво будинку — ідеальна аналогія Builder:
  • Архітектурний план (Director) визначає, що потрібно побудувати
  • Будівельна бригада (Builder) виконує кроки: фундамент → стіни → дах → інтер'єр
  • Результат може бути різним: котедж, хмарочос, або креслення (різні продукти з одного процесу)
Так само Builder в програмуванні створює об'єкти крок за кроком, дозволяючи налаштовувати кожен крок незалежно.

Архітектура та Механіка

UML діаграма класів

Loading diagram...
classDiagram
    class Director {
        -Builder builder
        +Construct()
    }

    class IBuilder {
        <<interface>>
        +Reset()
        +BuildStepA()
        +BuildStepB()
        +BuildStepC()
    }

    class ConcreteBuilder {
        -Product product
        +Reset()
        +BuildStepA()
        +BuildStepB()
        +BuildStepC()
        +GetProduct() Product
    }

    class Product {
        +PartA
        +PartB
        +PartC
    }

    class Client

    Director --> IBuilder : використовує
    ConcreteBuilder ..|> IBuilder : реалізує
    ConcreteBuilder --> Product : створює
    Client --> Director : керує
    Client --> ConcreteBuilder : отримує Product

    style Director fill:#f59e0b,stroke:#b45309,color:#ffffff
    style IBuilder fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style ConcreteBuilder fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style Product fill:#64748b,stroke:#334155,color:#ffffff
    style Client fill:#64748b,stroke:#334155,color:#ffffff

Діаграма послідовності

Loading diagram...
sequenceDiagram
    participant Client
    participant Director
    participant Builder
    participant Product

    Client->>+Builder: new ConcreteBuilder()
    Client->>+Director: new Director(builder)

    Client->>Director: Construct()
    activate Director
    Director->>Builder: Reset()
    Director->>Builder: BuildStepA()
    Director->>Builder: BuildStepB()
    Director->>Builder: BuildStepC()
    deactivate Director

    Client->>Builder: GetProduct()
    Builder->>Product: create Product
    Builder-->>Client: return Product

    Note over Client,Product: Продукт створений покроково

    style Director fill:#f59e0b,stroke:#b45309,color:#ffffff
    style Builder fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style Product fill:#64748b,stroke:#334155,color:#ffffff
    style Client fill:#64748b,stroke:#334155,color:#ffffff

Учасники та їх ролі

УчасникРольВідповідальність
Builder (інтерфейс)Абстрактний будівельникВизначає кроки створення продукту
ConcreteBuilderКонкретний будівельникРеалізує кроки та зберігає результат
ProductПродуктСкладний об'єкт, який створюється
Director (опціонально)ДиректорВизначає порядок кроків конструювання
ClientКлієнтІніціює процес та отримує результат

Практична Реалізація

Варіація 1: Classic Builder (з інтерфейсом)

Класичний підхід GoF з явним інтерфейсом Builder.
ICarBuilder.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Інтерфейс Builder визначає кроки створення продукту
/// </summary>
public interface ICarBuilder
{
    void Reset();
    void SetSeats(int number);
    void SetEngine(string engineType);
    void SetTripComputer(bool hasTripComputer);
    void SetGPS(bool hasGPS);
}
Car.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Продукт - складний об'єкт з багатьма частинами
/// </summary>
public class Car
{
    public int Seats { get; set; }
    public string Engine { get; set; } = string.Empty;
    public bool HasTripComputer { get; set; }
    public bool HasGPS { get; set; }

    public override string ToString()
    {
        return $"Car: {Seats} seats, {Engine} engine, " +
               $"TripComputer: {HasTripComputer}, GPS: {HasGPS}";
    }
}
CarBuilder.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Конкретний Builder для створення Car
/// </summary>
public class CarBuilder : ICarBuilder
{
    private Car _car = new();

    public void Reset()
    {
        _car = new Car();
    }

    public void SetSeats(int number)
    {
        _car.Seats = number;
    }

    public void SetEngine(string engineType)
    {
        _car.Engine = engineType;
    }

    public void SetTripComputer(bool hasTripComputer)
    {
        _car.HasTripComputer = hasTripComputer;
    }

    public void SetGPS(bool hasGPS)
    {
        _car.HasGPS = hasGPS;
    }

    /// <summary>
    /// Повертає готовий продукт та скидає builder для наступного використання
    /// </summary>
    public Car GetProduct()
    {
        Car result = _car;
        Reset(); // Підготовка до створення нового об'єкту
        return result;
    }
}
CarManualBuilder.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Інший Builder створює РІЗНИЙ продукт (Manual замість Car)
/// використовуючи ТІ Ж САМІ кроки
/// </summary>
public class CarManualBuilder : ICarBuilder
{
    private Manual _manual = new();

    public void Reset()
    {
        _manual = new Manual();
    }

    public void SetSeats(int number)
    {
        _manual.AddSection($"Інструкція по використанню {number} сидінь.");
    }

    public void SetEngine(string engineType)
    {
        _manual.AddSection($"Обслуговування двигуна типу: {engineType}.");
    }

    public void SetTripComputer(bool hasTripComputer)
    {
        if (hasTripComputer)
            _manual.AddSection("Інструкція по використанню бортового комп'ютера.");
    }

    public void SetGPS(bool hasGPS)
    {
        if (hasGPS)
            _manual.AddSection("Налаштування GPS навігації.");
    }

    public Manual GetProduct()
    {
        Manual result = _manual;
        Reset();
        return result;
    }
}

public class Manual
{
    private readonly List<string> _sections = new();

    public void AddSection(string section)
    {
        _sections.Add(section);
    }

    public override string ToString()
    {
        return $"Manual:\n  - {string.Join("\n  - ", _sections)}";
    }
}
Використання (без Director):
Program.cs
using DesignPatterns.Creational.Builder;

// Клієнт самостійно конфігурує Builder
var carBuilder = new CarBuilder();

carBuilder.SetSeats(4);
carBuilder.SetEngine("V8");
carBuilder.SetTripComputer(true);
carBuilder.SetGPS(true);

Car car = carBuilder.GetProduct();
Console.WriteLine(car);

// Той самий builder можна використати повторно
carBuilder.SetSeats(2);
carBuilder.SetEngine("V6");
Car sportsCar = carBuilder.GetProduct();
Console.WriteLine(sportsCar);

Варіація 2: Fluent Builder (Method Chaining)

Найпопулярніша варіація в C# — використання Fluent Interface з method chaining.
HttpRequest.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Продукт - HTTP запит
/// </summary>
public class HttpRequest
{
    public string Url { get; init; } = string.Empty;
    public string Method { get; init; } = "GET";
    public Dictionary<string, string> Headers { get; init; } = new();
    public Dictionary<string, string> QueryParams { get; init; } = new();
    public string? Body { get; init; }
    public TimeSpan Timeout { get; init; } = TimeSpan.FromSeconds(30);

    public override string ToString()
    {
        var headers = string.Join(", ", Headers.Select(kv => $"{kv.Key}: {kv.Value}"));
        var queryParams = string.Join("&", QueryParams.Select(kv => $"{kv.Key}={kv.Value}"));
        var url = string.IsNullOrEmpty(queryParams) ? Url : $"{Url}?{queryParams}";

        return $"{Method} {url}\nHeaders: {headers}\nBody: {Body ?? "None"}\nTimeout: {Timeout.TotalSeconds}s";
    }
}
HttpRequestBuilder.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Fluent Builder з method chaining
/// </summary>
public class HttpRequestBuilder
{
    private string _url = string.Empty;
    private string _method = "GET";
    private readonly Dictionary<string, string> _headers = new();
    private readonly Dictionary<string, string> _queryParams = new();
    private string? _body;
    private TimeSpan _timeout = TimeSpan.FromSeconds(30);

    /// <summary>
    /// Встановлює URL. Повертає this для method chaining.
    /// </summary>
    public HttpRequestBuilder WithUrl(string url)
    {
        _url = url;
        return this; // ✅ Ключ до Fluent Interface
    }

    public HttpRequestBuilder WithMethod(string method)
    {
        _method = method.ToUpper();
        return this;
    }

    public HttpRequestBuilder WithHeader(string key, string value)
    {
        _headers[key] = value;
        return this;
    }

    public HttpRequestBuilder WithQueryParam(string key, string value)
    {
        _queryParams[key] = value;
        return this;
    }

    public HttpRequestBuilder WithBody(string body)
    {
        _body = body;
        return this;
    }

    public HttpRequestBuilder WithTimeout(TimeSpan timeout)
    {
        _timeout = timeout;
        return this;
    }

    /// <summary>
    /// Фінальний метод, який створює та повертає продукт
    /// </summary>
    public HttpRequest Build()
    {
        if (string.IsNullOrWhiteSpace(_url))
            throw new InvalidOperationException("URL є обов'язковим параметром");

        return new HttpRequest
        {
            Url = _url,
            Method = _method,
            Headers = new Dictionary<string, string>(_headers),
            QueryParams = new Dictionary<string, string>(_queryParams),
            Body = _body,
            Timeout = _timeout
        };
    }
}
Використання:
Program.cs
using DesignPatterns.Creational.Builder;

// ✅ Читабельний та зрозумілий API
var request = new HttpRequestBuilder()
    .WithUrl("https://api.example.com/users")
    .WithMethod("POST")
    .WithHeader("Authorization", "Bearer token123")
    .WithHeader("Content-Type", "application/json")
    .WithQueryParam("page", "1")
    .WithQueryParam("limit", "10")
    .WithBody("{\"name\": \"John Doe\", \"email\": \"john@example.com\"}")
    .WithTimeout(TimeSpan.FromSeconds(60))
    .Build();

Console.WriteLine(request);

// Output:
// POST https://api.example.com/users?page=1&limit=10
// Headers: Authorization: Bearer token123, Content-Type: application/json
// Body: {"name": "John Doe", "email": "john@example.com"}
// Timeout: 60s
Переваги Fluent Builder
  • Читабельність: Код читається як англійське речення
  • Гнучкість: Можна пропустити опціональні параметри
  • Автозавершення IDE: IntelliSense підказує доступні методи
  • Immutability: Продукт можна зробити незмінним через { get; init; } ::

Варіація 3: Director для стандартних конфігурацій

Director інкапсулює типові конфігурації, дозволяючи створювати популярні варіанти одним викликом.
CarDirector.cs
namespace DesignPatterns.Creational.Builder;

/// <summary>
/// Director знає, як створювати популярні конфігурації автомобілів
/// </summary>
public class CarDirector
{
    private readonly ICarBuilder _builder;

    public CarDirector(ICarBuilder builder)
    {
        _builder = builder;
    }

    /// <summary>
    /// Створює спортивний автомобіль
    /// </summary>
    public void ConstructSportsCar()
    {
        _builder.Reset();
        _builder.SetSeats(2);
        _builder.SetEngine("V8 Twin-Turbo");
        _builder.SetTripComputer(true);
        _builder.SetGPS(true);
    }

    /// <summary>
    /// Створює міський автомобіль
    /// </summary>
    public void ConstructCityCar()
    {
        _builder.Reset();
        _builder.SetSeats(4);
        _builder.SetEngine("Inline-4");
        _builder.SetTripComputer(false);
        _builder.SetGPS(true);
    }

    /// <summary>
    /// Створює позашляховик (SUV)
    /// </summary>
    public void ConstructSUV()
    {
        _builder.Reset();
        _builder.SetSeats(7);
        _builder.SetEngine("V6");
        _builder.SetTripComputer(true);
        _builder.SetGPS(true);
    }
}
Використання з Director:
Program.cs
using DesignPatterns.Creational.Builder;

var carBuilder = new CarBuilder();
var director = new CarDirector(carBuilder);

// Створюємо спортивний автомобіль
director.ConstructSportsCar();
Car sportsCar = carBuilder.GetProduct();
Console.WriteLine("Sports Car: " + sportsCar);

// Створюємо міський автомобіль
director.ConstructCityCar();
Car cityCar = carBuilder.GetProduct();
Console.WriteLine("City Car: " + cityCar);

// Створюємо інструкцію для SUV
var manualBuilder = new CarManualBuilder();
director = new CarDirector(manualBuilder);
director.ConstructSUV();
Manual suvManual = manualBuilder.GetProduct();
Console.WriteLine(suvManual);
Коли використовувати Director?
  • Коли є стандартні конфігурації (наприклад, "Basic", "Premium", "Deluxe")
  • Коли потрібно приховати складність створення від клієнта
  • Коли один і той же процес створює різні продукти (Car vs Manual)
Коли НЕ потрібен Director?
  • При використанні Fluent Builder (метод Build() заміняє Director)
  • Коли клієнт бажає повного контролю над кроками ::

Варіація 4: Builder з валідацією та вимогами

Покращений Fluent Builder з обов'язковими полями та валідацією.
UserBuilder.cs
namespace DesignPatterns.Creational.Builder;

public class User
{
    public required string Username { get; init; }
    public required string Email { get; init; }
    public string? FirstName { get; init; }
    public string? LastName { get; init; }
    public int Age { get; init; }
    public string[] Roles { get; init; } = Array.Empty<string>();

    public override string ToString()
    {
        return $"User: {Username}, Email: {Email}, Name: {FirstName} {LastName}, " +
               $"Age: {Age}, Roles: [{string.Join(", ", Roles)}]";
    }
}

public class UserBuilder
{
    private string? _username;
    private string? _email;
    private string? _firstName;
    private string? _lastName;
    private int _age;
    private readonly List<string> _roles = new();

    public UserBuilder WithUsername(string username)
    {
        if (string.IsNullOrWhiteSpace(username))
            throw new ArgumentException("Username не може бути порожнім", nameof(username));

        _username = username;
        return this;
    }

    public UserBuilder WithEmail(string email)
    {
        if (!email.Contains('@'))
            throw new ArgumentException("Невалідний email", nameof(email));

        _email = email;
        return this;
    }

    public UserBuilder WithName(string firstName, string lastName)
    {
        _firstName = firstName;
        _lastName = lastName;
        return this;
    }

    public UserBuilder WithAge(int age)
    {
        if (age < 0 || age > 150)
            throw new ArgumentException("Невалідний вік", nameof(age));

        _age = age;
        return this;
    }

    public UserBuilder AddRole(string role)
    {
        if (!_roles.Contains(role))
            _roles.Add(role);
        return this;
    }

    public User Build()
    {
        // Валідація обов'язкових полів
        if (string.IsNullOrWhiteSpace(_username))
            throw new InvalidOperationException("Username є обов'язковим");

        if (string.IsNullOrWhiteSpace(_email))
            throw new InvalidOperationException("Email є обов'язковим");

        return new User
        {
            Username = _username,
            Email = _email,
            FirstName = _firstName,
            LastName = _lastName,
            Age = _age,
            Roles = _roles.ToArray()
        };
    }
}
Використання:
Program.cs
using DesignPatterns.Creational.Builder;

try
{
    var user = new UserBuilder()
        .WithUsername("john_doe")
        .WithEmail("john@example.com")
        .WithName("John", "Doe")
        .WithAge(30)
        .AddRole("Admin")
        .AddRole("User")
        .Build();

    Console.WriteLine(user);
}
catch (Exception ex)
{
    Console.WriteLine($"Помилка: {ex.Message}");
}

// Спроба створити невалідного користувача
try
{
    var invalidUser = new UserBuilder()
        .WithEmail("invalid-email") // Помилка валідації
        .Build();
}
catch (ArgumentException ex)
{
    Console.WriteLine($"Помилка валідації: {ex.Message}");
}

Реальні приклади з .NET

Приклад 1: StringBuilder (вбудований Builder в .NET)

StringBuilderкласичний приклад Builder патерну в .NET.
StringBuilderExample.cs
using System.Text;

// StringBuilder використовує Builder патерн для ефективного створення строк
var builder = new StringBuilder();

builder
    .Append("Hello, ")
    .Append("World!")
    .AppendLine()
    .Append("Builder pattern ")
    .Append("in action.");

string result = builder.ToString(); // Фінальний метод Build()
Console.WriteLine(result);

// Output:
// Hello, World!
// Builder pattern in action.

Приклад 2: UriBuilder

UriBuilderExample.cs
// UriBuilder - ще один вбудований Builder в .NET
var uriBuilder = new UriBuilder
{
    Scheme = "https",
    Host = "api.example.com",
    Port = 443,
    Path = "/v1/users",
    Query = "page=1&limit=10"
};

Uri uri = uriBuilder.Uri;
Console.WriteLine(uri); // https://api.example.com/v1/users?page=1&limit=10

Приклад 3: EF Core Query Builder (Fluent API)

Entity Framework Core використовує Fluent Builder для побудови запитів до БД.
EFCoreExample.cs
using Microsoft.EntityFrameworkCore;

// Fluent API в EF Core - це Builder патерн
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>()
        .HasKey(u => u.Id);

    modelBuilder.Entity<User>()
        .Property(u => u.Username)
        .IsRequired()
        .HasMaxLength(50);

    modelBuilder.Entity<User>()
        .HasMany(u => u.Orders)
        .WithOne(o => o.User)
        .HasForeignKey(o => o.UserId);
}

Приклад 4: ASP.NET Core WebApplicationBuilder

Program.cs
using Microsoft.AspNetCore.Builder;

// WebApplicationBuilder - Builder патерн для конфігурації ASP.NET Core додатку
var builder = WebApplication.CreateBuilder(args);

// Покрокова конфігурація
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build(); // ✅ Фінальний Build()

app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthorization();
app.MapControllers();

app.Run();

Patterns & Anti-patterns

✅ Best Practices

Крок 1: Використовуйте Fluent Interface для зручності

Повертайте this з кожного методу Builder для method chaining.

public QueryBuilder Where(string condition)
{
    _conditions.Add(condition);
    return this; // ✅ Дозволяє .Where().OrderBy().Limit()
}

Крок 2: Робіть продукт незмінним (immutable)

Використовуйте { get; init; } для властивостей продукту.

public class Product
{
    public string Name { get; init; } = string.Empty;
    public decimal Price { get; init; }
}

Крок 3: Валідуйте дані в методі Build()

Перевіряйте обов'язкові параметри перед створенням продукту.

public Product Build()
{
    if (string.IsNullOrEmpty(_name))
        throw new InvalidOperationException("Name є обов'язковим");

    // Створення продукту
}

Крок 4: Використовуйте Reset() для повторного використання

Дозвольте створювати кілька об'єктів одним Builder.

public void Reset()
{
    _product = new Product();
}

❌ Антипатерни та підводні камені

1. Надмірне ускладнення

Проблема:
Використання Builder для простих об'єктів з 2-3 параметрами.
// ❌ АНТИПАТЕРН: Builder для простого об'єкту
var point = new PointBuilder()
    .WithX(10)
    .WithY(20)
    .Build();

// ✅ ПРАВИЛЬНО: Простий конструктор або record
var point = new Point(10, 20);
// або
var point = new Point { X = 10, Y = 20 };
Правило:
Builder виправданий лише для об'єктів з 4+ параметрами або складною логікою створення.

2. Змінюваний (mutable) продукт

Проблема:
Продукт з публічними сеттерами дозволяє змінювати стан після створення.
// ❌ АНТИПАТЕРН: Змінюваний продукт
public class User
{
    public string Username { get; set; } // Можна змінити після створення!
    public string Email { get; set; }
}

var user = builder.Build();
user.Username = "hacker"; // Небезпечно!
Рішення:
Використовуйте { get; init; } або readonly.
// ✅ ПРАВИЛЬНО: Незмінний продукт
public class User
{
    public string Username { get; init; } = string.Empty;
    public string Email { get; init; } = string.Empty;
}

3. Відсутність валідації

Проблема:
Builder створює невалідний об'єкт.
// ❌ АНТИПАТЕРН: Без валідації
public HttpRequest Build()
{
    return new HttpRequest { Url = _url }; // _url може бути null!
}
Рішення:
Валідуйте в Build().
// ✅ ПРАВИЛЬНО: Валідація обов'язкових полів
public HttpRequest Build()
{
    if (string.IsNullOrWhiteSpace(_url))
        throw new InvalidOperationException("URL є обов'язковим");

    return new HttpRequest { Url = _url };
}

Troubleshooting

Проблема 1: NullReferenceException в Build()

Симптоми:
System.NullReferenceException: Object reference not set to an instance of an object.
Причина:
Обов'язкове поле не було встановлене.Рішення:
Додайте перевірку в методі Build().
public User Build()
{
    if (_username == null)
        throw new InvalidOperationException("Username обов'язковий. Використайте WithUsername().");

    return new User { Username = _username, ... };
}

Проблема 2: Builder створює один об'єкт замість багатьох

Симптоми:
var builder = new CarBuilder();
builder.SetSeats(2);
var car1 = builder.GetProduct();

builder.SetSeats(4);
var car2 = builder.GetProduct();

Console.WriteLine(car1.Seats); // 4 (очікували 2!)
Причина:
Builder не викликає Reset() після GetProduct().Рішення:
Автоматично скидайте в GetProduct().
public Car GetProduct()
{
    Car result = _car;
    Reset(); // ✅ Скидаємо для наступного використання
    return result;
}

Проблема 3: Проблеми з наслідуванням Builder

Симптоми:
При спробі створити базовий Builder для успадкування, method chaining не працює.
public class BaseBuilder
{
    public BaseBuilder WithId(int id) { ... return this; }
}

public class DerivedBuilder : BaseBuilder
{
    public DerivedBuilder WithName(string name) { ... return this; }
}

// ❌ Не компілюється:
new DerivedBuilder()
    .WithId(1)      // Повертає BaseBuilder
    .WithName("X"); // Помилка: BaseBuilder не має WithName()
Рішення:
Використовуйте generic base class з self типом.
public class BaseBuilder<TSelf> where TSelf : BaseBuilder<TSelf>
{
    public TSelf WithId(int id)
    {
        // ...
        return (TSelf)this;
    }
}

public class DerivedBuilder : BaseBuilder<DerivedBuilder>
{
    public DerivedBuilder WithName(string name)
    {
        // ...
        return this;
    }
}

// ✅ Тепер працює
new DerivedBuilder()
    .WithId(1)
    .WithName("X");

Практичні Завдання

Рівень 1: Базовий


Рівень 2: Середній


Рівень 3: Експертний


Підсумок

Ключові висновки

Що таке Builder?

Породжуючий патерн для поетапного створення складних об'єктів з зрозумілим API.

Коли використовувати?

Складні об'єкти з 4+ параметрами, телескопічні конструктори, SQL/HTTP запити.

Fluent Interface

Повертайте this з кожного методу для method chaining.

Director

Опціональний. Використовуйте для стандартних конфігурацій.

::

Порівняння Builder з альтернативами

ПідхідПеревагиНедоліки
КонструкторПростота, immutabilityБагато параметрів незрозумілі
СеттериГнучкістьЗмінюваний стан, можливість невалідного об'єкта
BuilderЧитабельність, валідація, immutabilityБільше коду
Init PropertiesПроста immutabilityНемає валідації при створенні

Посилання на наступні патерни

  • Factory Method — делегування створення об'єктів підкласам
  • Abstract Factory — створення сімейств пов'язаних об'єктів
  • Prototype — клонування існуючих об'єктів

Додаткові ресурси