Строитель позволяет отделить процесс создания сложного объекта от его реализации. При этом, результатом одних и тех же операций могут быть различные объекты.
Данный шаблон используется в случае, если:
- процесс создания объекта можно разделить на части (шаги);
- (и) алгоритм этого процесса не должен зависеть от того, из каких частей состоит объект;
- (и) конструирование должно обеспечивать возможность создавать различные объекты.
Лучше понять работу Строителя можно используя такое сравнение: многие порождающие шаблоны, используя конкретные исходные данные, выдают обобщенный результат (интерфейс объекта). Строитель же наоборот, используя обобщенный набор данных, создает известную клиенту конкретную реализацию.
Обратите внимание:
- данный шаблон не скрывает реализацию порождаемых объектов, а создает то, что требуется;
- как следствие, результатом работы могут быть объекты, не связанные явно между собой. Как правило, у них единые цели, но не обязательно есть общие интерфейсы, базовые классы и т.д.
Шаблон Строитель включает двух участников процесса:
- Строитель (Builder) – предоставляет методы для сборки частей объекта, при необходимости преобразовывает исходные данные в нужный вид, создает и выдает объект;
- Распорядитель (Director) – определяет стратегию сборки: собирает данные и определяет порядок вызовов методов Строителя.
Может возникнуть вопрос: если можно напрямую вызывать методы Строителя, то зачем нужен Распорядитель? Его задача – сокрытие стратегии сборки. Это позволит, при необходимости, модифицировать или даже полностью менять ее, не затрагивая остальной код.
Так же Распорядитель, как правило, отвечает за получение данных для конструирования. И уже потом, Строитель преобразовывает их в вид, необходимый для порождаемого объекта. Такое разделение связано с тем, что создаваемый объект скрыт от Распорядителя и, кроме того, может не уметь работать с форматом исходных данных.
Схожие шаблоны и их отличия
Реализация шаблона в общем виде
- определяем шаги конструирования сложного объекта, и на их основе разрабатываем интерфейс Строителя IBuilder;
- если планируется несколько стратегий сборки, то создаем интерфейс Распорядителя IDirector;
- разрабатываем класс Распорядителя MyDirector (реализующий IDirector), работающий со Строителями через интерфейс IBuilder;
- создаем класс Строителя MyBuilder, реализующий интерфейс IBuilder и метод получения результата;
- в клиентском коде экземпляру MyDirector передаем интерфейс IBuilder экземпляра MyBuilder;
- запускаем процесс сборки, вызвав метод Распорядителя;
- получаем созданный экземпляр MyProduct у используемой реализации Строителя MyBuilder.
Возможно возникнет вопрос о необходимости создания интерфейсов. Почему не перейти сразу к классам? Потому, что такой подход позволит в дальнейшем, изменяя реализации Строителя и Распорядителя, влиять результат конструирования. Однако, если не планируются разные стратегии сборки, то разработку интерфейса Распорядителя можно пропустить.
Пример реализации
Создадим класс телефона Phone который будет являться результатом производства нашей системы
Создадим интерфейс разработчика телефонов IDeveloper
Создадим конкретный класс разработчика телефонов, реализующий интерфейс IDeveloper
По аналогии создадим класс IphoneDeveloper
Для управления разработчиками создаем класс Director
Посмотрим применение
Запустим программу