Паттерн Строитель (Builder) — уровень объекта
Название и классификация паттерна
Строитель — паттерн, порождающий объекты.
Назначение
Отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.
Применимость
Паттерн Builder может помочь в решении следующих задач:
- • В системе могут существовать сложные объекты, создание которых за одну операцию затруднительно или невозможно. Требуется поэтапное построение объектов с контролем результатов выполнения каждого этапа.
- • Алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой.
- • Процесс конструирования должен обеспечивать различные представления конструируемого объекта.
Структура
Структура паттерна Строитель (Builder) представлена на рис. 37.

Рис. 37. UML-диаграмма классов паттерна Builder
Участники
Director — распорядитель: конструирует объект, пользуясь интерфейсом Builder.
Builder — строитель: задает абстрактный интерфейс для создания частей объекта Product.
ConcreteBuilder — конкретный строитель:
- • конструирует и собирает вместе части продукта посредством реализации интерфейса Builder;
- • определяет создаваемое представление и следит за ним;
- • предоставляет интерфейс для доступа к продукту.
Product — продукт:
- • представляет сложный конструируемый объект. ConcreteBuilder строит внутреннее представление продукта и определяет процесс его сборки;
- • включает классы, которые определяют составные части, в том числе интерфейсы для сборки конечного результата из частей.
Отношения
На диаграмме последовательностей (рис. 38) показано взаимодействие элементов структуры паттерна.
Клиент создает объект-распорядитель Director и конфигурирует его нужным объектом-строителем Builder.
Распорядитель уведомляет строителя о том, что нужно построить очередную часть продукта.
Строитель обрабатывает запросы распорядителя и добавляет новые части к продукту.
Клиент забирает продукт у строителя.

Рис. 38. UML-диаграмма последовательностей паттерна Builder
Результаты
Плюсы и минусы паттерна Строитель и его применения:
- • позволяет изменять внутреннее представление продукта. Объект Builder предоставляет распорядителю абстрактный интерфейс для конструирования продукта, за которым он может скрыть представление и внутреннюю структуру продукта, а также процесс его сборки. Поскольку продукт конструируется через абстрактный интерфейс, то для изменения внутреннего представления достаточно всего лишь определить новый вид строителя;
- • изолирует код, реализующий конструирование и представление. Паттерн строитель улучшает модульность, инкапсулируя способ конструирования и представления сложного объекта. Клиентам ничего не надо знать о классах, определяющих внутреннюю структуру продукта, они отсутствуют в интерфейсе строителя. Каждый конкретный строитель ConcreteBuilder содержит весь код, необходимый для создания и сборки конкретного вида продукта. Код пишется только один раз, после чего разные распорядители могут использовать его повторно для построения вариантов продукта из одних и тех же частей;
- • дает более тонкий контроль над процессом конструирования. В отличие от порождающих паттернов, которые сразу конструируют весь объект целиком, строитель делает это шаг за шагом под управлением распорядителя. И лишь когда продукт завершен, распорядитель забирает его у строителя. Поэтому интерфейс строителя в большей степени отражает процесс конструирования продукта, нежели другие порождающие паттерны. Это позволяет обеспечить более тонкий контроль над процессом конструирования, а значит, и над внутренней структурой готового продукта.
Реализация
Обычно существует абстрактный класс Builder, в котором определены операции для каждого компонента, который распорядитель может «попросить» создать. По умолчанию эти операции ничего не делают. Но в классе конкретного строителя ConcreteBuilder они замещены для тех компонентов, в создании которых он принимает участие.
Вот еще некоторые достойные внимания вопросы реализации:
- • интерфейс сборки и конструирования. Интерфейс класса Builder должен быть достаточно общим, чтобы обеспечить конструирование при любом виде конкретного строителя. Ключевой вопрос проектирования связан с выбором модели процесса конструирования и сборки. Обычно бывает достаточно модели, в которой результаты выполнения запросов на конструирование просто добавляются к продукту. Но иногда может потребоваться доступ к частям сконструированного к данному моменту продукта. Например, деревья синтаксического разбора строятся снизу вверх;
- • почему нет абстрактного класса для продуктов. В типичном случае продукты, изготавливаемые различными строителями, имеют настолько разные представления, что изобретение для них общего родительского класса ничего не дает. Поскольку клиент обычно конфигурирует распорядителя подходящим конкретным строителем, то, надо полагать, ему известно, какой именно подкласс класса Builder используется и как нужно обращаться с произведенными продуктами;
- • пустые методы класса Builder по умолчанию. В C++ методы строителя намеренно не объявлены чисто виртуальными функциями-членами. Вместо этого они определены как пустые функции, что позволяет подклассу замешать только те операции, в которых он заинтересован.
Пример кода
Приведем реализацию паттерна Builder на примере построения армий для военной стратегии «Пунические войны». Такие рода войск, как пехота, лучники и конница, для обеих армий идентичны. С целью демонстрации возможностей паттерна Builder введем новые виды боевых единиц:
- • катапульты для армии Рима;
- • боевые слоны для армии Карфагена.
#include
#include
// Классы всех возможных родов войск class Infantryman {
public: void info() {
co?t << "Infantryman" << endl;
}
};
class Archer
{
public: void info() {
cout << "Archer" << endl;
}
};
class Horseman
{
public: void info() {
cout << "Horseman" << endl;
}
};
class Catapult
{
public: void info() {
cout << "Catapult" << endl;
}
class Elephant
{
public: void info() {
cout << "Elephant" << endl;
}
// Класс "Армия", содержащий все типы боевых единиц class Army {
public;
vector
for(i=0; i } }; // Базовый класс ArmyBuilder объявляет интерфейс для поэтапного // построения армии и предусматривает его реализацию по умолчанию class ArmyBuilder { protected: Army* p; public: ArmyBuilder(): p(0) {} virtual ~ArmyBuilder() {} virtual void createArmyO {} virtual void buildlnfantryman() {} virtual void buildArcher() {} virtual void buildHorseman() {} virtual void buildCatapult() {} virtual void buildElephant() {} virtual Army* getArmy() { return p;} // Римская армия имеет все типы боевых единиц, кроме боевых слонов class RomanArmyBuilder: public ArmyBuilder { public: void createArmyO { p = new Army;} void buildlnfantrymanO { p->vi.push_back( Infantryman());} void buildArcher() { p->va.push_back( Archer());} void buildHorseman() { p->vh.push_back( Horseman());} void buildCatapult() { p->vc.push_back( Catapult());} }; // Армия Карфагена имеет все типы боевых единиц, кроме катапульт class CarthaginianArmyBuilder: public ArmyBuilder { public: void createArmyO { P = new Army;} void buildlnfantrymanO { p->vi.push_back( Infantryman());} void buildArcher() { p->va.push_back( Archer());} void buildHorsemanO { p->vh.push_back( Horseman());} void buildElephant() { p->ve.push_back( Elephant());} }; // Класс-распорядитель, поэтапно создающий армию той или иной // стороны. // Именно здесь определен алгоритм построения армии, class Director { public: Army* createArmy( ArmyBuilder & builder) { builder. createArmyO; builder.buildlnfantrymanO; builder.buildArcher(); builder.buildHorsemanO; builder.buildCatapult(); builder.buildElephant(); return( builder.getArmyO); } int main() { Director dir; RomanArmyBuilder ra_builder; CarthaginianArmyBuilder ca_builder; Army * ra = dir.createArmy( ra_builder); Army * ca = d i r. create Army ( ca_builder); cout << "Roman army:" << endl; ra->info(); cout << "
Carthaginian army:" << endl; ca->info(); //••• return 0; } Вывод программы будет следующим: Roman army: Infantryman Archer Horseman Catapult Carthaginian army: Infantryman Archer Horseman Elephant Очень часто базовый класс строителя (в коде выше это Army Builder) не только объявляет интерфейс для построения частей продукта, но и определяет ничего не делающую реализацию по умолчанию. Тогда соответствующие подклассы (RomanArmyBuilder, CarthaginianArmyBuilder) переопределяют только те методы, которые участвуют в построении текущего объекта. Так, класс RomanArmyBuilder не определяет метод buildElephant(), поэтому римская армия не может иметь слонов. А в классе CanhaginianArmyBuilder не определен buildCatapult(), поэтому армия Карфагена не может иметь катапульты. Достоинства паттерна Builder Возможность контролировать процесс создания сложного продукта. Возможность получения разных представлений некоторых данных. Недостатки паттерна Builder Concrete Builder и создаваемый им продукт жестко связаны между собой, поэтому при внесеннии изменений в класс продукта скорее всего придется соответствующим образом изменять и класс Concrete Builder. Родственные паттерны Абстрактная фабрика похожа на строитель в том смысле, что может конструировать сложные объекты. Основное различие между ними в том, что строитель делает акцент на пошаговом конструировании объекта, а абстрактная фабрика — на создании семейств объектов (простых или сложных). Строитель возвращает продукт на последнем шаге, тогда как с точки зрения абстрактной фабрики продукт возвращается немедленно. Паттерн Компоновщик — это то, что часто создает строитель.