Также известен как Builder

Строитель

Суть паттерна

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

Проблема

У вас есть объект, который требует кропотливой пошаговой инициализации многих полей и вложенных объектов. Этот код либо упрятан в конструктор с множеством параметров, либо распылён по всему клиентскому коду.

Например, один из возможных продуктов — Дом. Чтобы его построить нужно поставить 4 стены, установить двери и два окна, постелить крышу. Но в некоторых случаях вам нужен дом побольше, посветлее, с чёрным ходом и прочим добром.

Проблему можно решить в лоб, создав подклассы домов для всех этих вариаций. Но в реальности их будет слишком много, да и в придачу вы получите все проблемы, связанные с наследованием.

С другой стороны, вы можете создать гигантский конструктор Дома, принимающий уйму параметров для контроля над создаваемым продуктом. Но большая часть этих параметров будет простаивать, а вызовы конструктора будут выглядеть монструозно из-за длинного списка параметров.

Решение

Паттерн Строитель предлагает определить и формализовать все шаги конструирования продукта в общем интерфейсе — Строителе.

Чтобы создать продукт, потребуется поочерёдно вызывать методы класса, реализующего интерфейс Строителя.

В программе может быть несколько видов Строителей, при условии, что все они реализуют общий интерфейс. Они могут по-разному реализовывать шаги строительства (например, делать стены из камня, бронированные двери и прочее). За счёт общего интерфейса, их можно взаимозаменять, чтобы получать продукты с разными свойствами.

Чтобы не загромождать клиентский код вызовами методов Строителя, можно ввести промежуточный класс Директор. В этом случае Директор будет отвечать за порядок вызова шагов, а Строитель — за реализацию этих шагов.

Структура

Схема структуры классов паттерна Строитель
  1. Строитель описывает общий интерфейс шагов создания продуктов.

  2. Конкретный Строитель содержит конкретную реализацию шагов строительства.

    Кроме шагов, может содержать метод получения результата. Этот метод не определён в интерфейсе, так как строители могут возвращать продукты разных классов, не входящих в одну иерархию. Но вы всегда можете добавить такой метод в интерфейс, если строители производят однотипные продукты.

  3. Продукт — создаваемый объект. Продукты разных конкретных строителей не обязаны иметь общий интерфейс.

  4. Директор конструирует продукт, используя объект-строитель. Обычно, Клиент подаёт в конструктор Директора уже готовый объект-строитель, и в дальнейшем данный директор использует только его. Но возможен и другой вариант, когда Клиент передаёт строителя через параметр строительного метода Директора.

  5. Отдельный класс Директора не является строго обязательным. Вы можете вызывать методы строителя и напрямую из клиентского кода, так как клиентский код знает о классах Строителей.

    Тем не менее Директор полезен, если у вас есть несколько способов конструирования продуктов, отличающихся порядком и наличием шагов конструирования. В этом случае, вы объединяете всю эту логику в одном классе.

Псевдокод

В этом примере Строитель используется для пошагового конструирования автомобилей. Директор производит различные модели автомобилей, используя разный набор шагов строительства. Директор работает с тем объектом-строителем, который ему подали. Это позволяет создавать руководства пользователей для различных моделей автомобилей, просто создав новый класс строителя.

Таким образом, паттерн Строитель позволяет создавать различные вариации продуктов, просто изменяя набор и порядок вызова шагов строительства. Кроме того, подав альтернативную версию объекта строителя, вы можете произвести совершенно другой тип продукта, используя тот же порядок строительства.

// Строитель может создавать различные продукты, используя один и тот же
// процесс строительства.
class Car is
    Can have GPS, trip computer and various numbers of seats.
    Can be a city car, a sports car, or a cabriolet.

class Manual is
    Textual representation of a car.


// Интерфейс Строителя объявляет все возможные этапы и шаги
// конфигурации продукта.
interface Builder is
    method setSeats(number)
    method setEngine(engine: Engine)
    method setTripComputer()
    method setGPS()

// Все конкретные строители реализуют общий интерфейс по-своему.
class CarBuilder implements Builder is
    method setSeats(number) is
        Tell the builder the number of seats.
    method setEngine(engine: Engine) is
        Install a given engine.
    method setTripComputer() is
        Install a trip computer.
    method setGPS() is
        Install a global positioning system.
    method getResult(): RealCar is
        Construct and return a real car.

// В отличие от других создающих паттернов, Строители могут создавать совершенно
// разные продукты, не имеющие общего интерфейса.
class CarManualBuilder implements Builder is
    method setSeats(number) is
        Document car seats features.
    method setEngine(engine: Engine) is
        Add an engine instruction.
    method setTripComputer() is
        Add a trip computer instruction.
    method setGPS() is
        Add GPS instruction.
    method getResult(): Manual is
        Get manual contents.


// Директор знает в какой последовательности заставлять работать строителя. Он
// работает с ним через общий интерфейс Строителя. Из-за этого, он может не
// знать какой конкретно продукт сейчас строится.
class Director is
    method constructSportsCar(builder: Builder) is
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setTripComputer()
        builder.setGPS()


// Директор получает объект конкретного строителя от клиента (приложения).
// Приложение само знает какой строитель использовать, чтобы получить
// нужный продукт.
class Application is
    method makeCar is
        director = new Director();

        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);
        Car car = builder.getResult();

        CarManualBuilder builder = new CarManualBuilder();
        director.constructSportsCar(builder);

        // Готовый продукт возвращает строитель, так как Директор чаще всего не
        // знает и не зависит от конкретных классов строителей и продуктов.
        Manual manual = builder.getResult();

Применимость

Проблема телескопического конструктора.

Это когда у вас есть один конструктор с десятью опциональными параметрами. Такой конструктор неудобно вызывать, поэтому вас есть ещё десять вспомогательных альтернативных конструкторов с меньшим количеством параметров.

Всё что они делают — переадресуют вызов к главному конструктору, подавая какие-то значения по умолчанию в опциональные параметры главного конструктора.

Паттерн Строитель позволяет создавать объекты, вызывая минимум шагов, требуемых для этого.

Именно поэтому Строители часто используют для создания неизменяемых объектов.

Ваш код создаёт разные представления какой-то одной сущности (например, деревянные и железо-бетонные дома). Создание этой сущности содержит одинаковые этапы, но отличается в деталях. Конечные продукты могут не быть потомками одного класса или интерфейса.

В этом случае, паттерн Строитель идеально подходит, чтобы собрать в одном месте код конструирования ваших сущностей.

Для каждой сущности нужно создать собственного Строителя, а этапы строительства переедут в общий класс-Директор.

Требуется конвертировать одну сущность в множество других. Например, HTML документ требуется превращать в PDF, DOC и TXT.

Это классический пример применения Строителей. Абстрактный строитель будет иметь методы конвертации для каждого HTML-тега.

Другие форматы поддерживают не все HTML теги, но они смогут определить только те шаги строительства, которые поддерживают.

Требуется создание дерева Компоновщика или другого составного объекта.

Паттерн Строитель конструирует объекты пошагово, а не за один проход. Более того, шаги строительства можно выполнять рекурсивно. А без этого безопасно не построить древовидную структуру вроде Компоновщика.

Шаги реализации

  1. Убедитесь в том, что у вас есть общие шаги построения продукта, а также вариации шагов, которые приводят к созданию различных представлений продуктов.

  2. Создайте интерфейс Строителя, с шагами для производства всех возможных представлений продуктов.

  3. Создайте классы Конкретных строителей для каждого из представлений. Реализуйте их шаги строительства.

  4. Подумайте о создании класса Директора. Его методы будут создавать различные конфигурации продуктов, вызывая разные шаги одного и того же строителя.

  5. Клиентский код должен создавать и объекты-Строители и объекты-Директоры. Строители подаются либо в конструктор Директора, либо в его строительный метод.

  6. Чтобы начать строительство, клиент должен вызывать метод объекта-Директора.

  7. Результат строительства можно вернуть из Директора, но только если все продукты имеют общий интерфейс. В обратном случае, каждый Строитель должен иметь собственный метод получения результата.

Преимущества и недостатки

  • Позволяет создавать продукты пошагово.
  • Позволяет использовать один и тот же код для создания различных продуктов.
  • Изолирует сложный код сборки продукта от его основной бизнес-логики.
  • Усложняет код программы за счёт дополнительных классов.

Отношения с другими паттернами

Реализация в различных языках программирования

Java