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

Аналогія

Допустимо, ви прийшли в забігайлівку, замовили бургер дня, і вам видали його без запитань. Це приклад «Проста фабрика». Але іноді логіка створення складається з більшої кількості кроків. Наприклад, при замовленні бургера є кілька варіантів хліба, начинки, соусів, додаткових інгредієнтів. У таких ситуаціях допомагає шаблон Будівник.

Коротко

Шаблон дозволяє створювати різні характеристики об'єкта, уникаючи забруднення конструктора (constructor pollution). Це корисно, коли об'єкт може мати кілька властивостей. Або коли створення об'єкта складається з великої кількості етапів.

Вікіпедія

Шаблон "Будівельник" призначений для пошуку вирішення проблеми антипаттерну Telescoping constructor.

Поясню, що таке антипаттер Telescoping constructor. Кожен із нас коли-небудь стикався з подібним конструктором:

public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)
{
}

Як бачите, кількість параметрів може швидко розростись, і важко буде розібратися в їх структурі. Крім того, цей список параметрів зростатиме і надалі, якщо в майбутньому ви захочете додати нові опції. Це і є антіпаттерн Telescoping constructor.

Приклад

Розумна альтернатива – шаблон «Будівельник». Спочатку створимо бургер:

class Burger
{
    protected $size;

    protected $cheese = false;
    protected $pepperoni = false;
    protected $lettuce = false;
    protected $ tomato = false;

    public function __construct(BurgerBuilder $builder)
    {
        $this->size = $builder->size;
        $this->cheese = $builder->cheese;
        $this->pepperoni = $builder->pepperoni;
        $this->lettuce = $builder->lettuce;
        $this->tomato = $builder->tomato;
    }
}

А потім додамо «будівника»:

class BurgerBuilder
{
    public $size;

    public $cheese = false;
    public $ pepperoni = false;
    $lettuce = false;
    public $ tomato = false;

    public function __construct(int $size)
    {
        $this->size = $size;
    }

    public function addPepperoni()
    {
        $this->pepperoni = true;
        return $this;
    }

    public function addLettuce()
    {
        $this->lettuce = true;
        return $this;
    }

    public function addCheese()
    {
        $this->cheese = true;
        return $this;
    }

    public function addTomato()
    {
        $this->tomato = true;
        return $this;
    }

    public function build(): Burger
    {
        return new Burger($this);
    }
}

Використання:

$burger = (new BurgerBuilder(14))
                    ->addPepperoni()
                    ->addLettuce()
                    ->addTomato()
                    ->build();

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

Коли об'єкт може мати кілька властивостей і коли потрібно уникнути Telescoping constructor. Ключова відмінність від шаблону "Проста фабрика": він використовується в одноетапному створенні, а "Будівельник" - у багатоетапному.