Приведи пример реализации паттерна Builder (Строитель) на PHP.

Ответ

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

Классическая реализация (из GoF):

// 1. Продукт, который мы строим
class Car
{
    private string $engine = '';
    private int $seats = 0;
    private bool $hasGPS = false;
    private bool $hasTripComputer = false;

    public function setEngine(string $engine): void { $this->engine = $engine; }
    public function setSeats(int $seats): void { $this->seats = $seats; }
    public function setGPS(bool $hasGPS): void { $this->hasGPS = $hasGPS; }
    public function setTripComputer(bool $hasTripComputer): void { $this->hasTripComputer = $hasTripComputer; }

    public function getDescription(): string
    {
        return sprintf(
            "Car with %s engine, %d seats%s%s.",
            $this->engine,
            $this->seats,
            $this->hasGPS ? ', GPS' : '',
            $this->hasTripComputer ? ', Trip Computer' : ''
        );
    }
}

// 2. Интерфейс Строителя
interface CarBuilder
{
    public function reset(): void;
    public function buildEngine(): void;
    public function buildSeats(): void;
    public function buildGPS(): void;
    public function buildTripComputer(): void;
    public function getResult(): Car;
}

// 3. Конкретный строитель для спортивного автомобиля
class SportsCarBuilder implements CarBuilder
{
    private Car $car;

    public function reset(): void { $this->car = new Car(); }
    public function buildEngine(): void { $this->car->setEngine('V8 Turbo'); }
    public function buildSeats(): void { $this->car->setSeats(2); }
    public function buildGPS(): void { $this->car->setGPS(true); } // В спортивной версии есть GPS
    public function buildTripComputer(): void { $this->car->setTripComputer(true); }
    public function getResult(): Car { return $this->car; }
}

// 4. Директор, который управляет процессом построения
class Director
{
    public function constructSportsCar(CarBuilder $builder): Car
    {
        $builder->reset();
        $builder->buildEngine();
        $builder->buildSeats();
        $builder->buildGPS();
        $builder->buildTripComputer();
        return $builder->getResult();
    }
}

// 5. Клиентский код
$builder = new SportsCarBuilder();
$director = new Director();

$car = $director->constructSportsCar($builder);
echo $car->getDescription(); // Car with V8 Turbo engine, 2 seats, GPS, Trip Computer.

Почему это полезно:

  • Изоляция сложной логики создания. Клиент (Director) не знает деталей сборки конкретной модели.
  • Гибкость. Можно легко добавить нового строителя для SUVBuilder с другим набором опций, используя тот же интерфейс.
  • Пошаговое конструирование. Методы build... можно вызывать в разном порядке или пропускать, если это позволяет строитель.

Ответ 18+ 🔞

Слушай, а вот Builder — это вообще огонь паттерн, когда тебе надо собрать какую-нибудь пиздопроебибну конструкцию по кирпичикам. Представь, ты не просто new Car() и в него двадцать сеттеров пихаешь, а у тебя есть чёткий план, какого хуя и в каком порядке собирать.

Вот смотри, классика жанра, прямо из умных книжек.

Разбираем по косточкам:

// 1. Это наш конечный продукт, который мы будем собирать
class Car
{
    private string $engine = '';
    private int $seats = 0;
    private bool $hasGPS = false;
    private bool $hasTripComputer = false;

    public function setEngine(string $engine): void { $this->engine = $engine; }
    public function setSeats(int $seats): void { $this->seats = $seats; }
    public function setGPS(bool $hasGPS): void { $this->hasGPS = $hasGPS; }
    public function setTripComputer(bool $hasTripComputer): void { $this->hasTripComputer = $hasTripComputer; }

    public function getDescription(): string
    {
        return sprintf(
            "Car with %s engine, %d seats%s%s.",
            $this->engine,
            $this->seats,
            $this->hasGPS ? ', GPS' : '',
            $this->hasTripComputer ? ', Trip Computer' : ''
        );
    }
}

Продукт — он как есть. Просто коробка с полями и сеттерами. Доверия ебать ноль — сам по себе он нихрена не умеет.

// 2. А вот это — контракт для всех строителей. Инструкция, блядь.
interface CarBuilder
{
    public function reset(): void;
    public function buildEngine(): void;
    public function buildSeats(): void;
    public function buildGPS(): void;
    public function buildTripComputer(): void;
    public function getResult(): Car;
}

Интерфейс говорит: хочешь быть строителем — умей делать вот эти пять действий. Не умеешь — иди ты нахуй, не строитель ты.

// 3. А вот конкретный чувак, который знает, как собрать тачку для гонок
class SportsCarBuilder implements CarBuilder
{
    private Car $car;

    public function reset(): void { $this->car = new Car(); }
    public function buildEngine(): void { $this->car->setEngine('V8 Turbo'); } // Двигатель — чтоб сосалки обзавидовались
    public function buildSeats(): void { $this->car->setSeats(2); } // Места только для водилы и тёлки
    public function buildGPS(): void { $this->car->setGPS(true); } // Нафиг ему GPS? А хуй его знает, но в комплектации есть!
    public function buildTripComputer(): void { $this->car->setTripComputer(true); }
    public function getResult(): Car { return $this->car; }
}

Этот парень — специалист. Он не будет ставить движок от трактора или шесть сидений. Он чётко знает рецепт спортивного болида.

// 4. А это — прораб, начальник стройки. Он командует, что делать.
class Director
{
    public function constructSportsCar(CarBuilder $builder): Car
    {
        $builder->reset(); // Начинаем с чистого листа
        $builder->buildEngine(); // Сперва движок, основа основ
        $builder->buildSeats(); // Потом сидушки
        $builder->buildGPS(); // Потом навигацию
        $builder->buildTripComputer(); // И наконец бортовик
        return $builder->getResult(); // Всё, забирай готовое!
    }
}

Директору да похуй, какой именно строитель пришёл — спортивный или внедорожный. Он тыкает пальцем в интерфейс и говорит: «Делай раз, делай два!». А тот уж сам знает, как именно делать.

// 5. Ну и мы, клиенты, которые всё это запускаем
$builder = new SportsCarBuilder();
$director = new Director();

$car = $director->constructSportsCar($builder);
echo $car->getDescription(); // Car with V8 Turbo engine, 2 seats, GPS, Trip Computer.

И вуаля! Мы получили тачку, даже не вникая, какой там хуй с винтом внутри у SportsCarBuilder.

В чём, блядь, соль?

  • Сложность спрятана. Клиенту (нам) не нужно знать, что спорткар собирается в другом порядке или с другими деталями, чем внедорожник. Волнение ебать — ноль. Прораб всё сделает.
  • Гибкость — овердохуища. Захотел грузовик — написал TruckBuilder, который ставит дизель и десять колёс. Прораб (Director) будет работать с ним по той же самой схеме, тыкая в те же методы buildEngine() и buildSeats(). Ёпта, красота!
  • Контроль над процессом. Можно собирать пошагово, можно пропускать шаги (если строитель позволяет). Хочешь тачку без GPS — просто не вызывай buildGPS(). Всё в твоих руках, чувак.

Короче, если объект — манда с ушами, с кучей опций и вариаций сборки, Builder — твой выбор. Не надо городить один хуй в пальто-конструктор на все случаи жизни.