«Строитель»
Суть этого паттерна – отделить процесс создания сложного объекта от его представления. Таким образом, можно получать различные представления объекта, используя один и тот же технологический процесс его создания. На первый взгляд этот паттерн кажется похожим на «Абстрактную фабрику», но отличия есть. Можно сказать, что «Абстрактная фабрика» концентрируется на том, что создается, а «Строитель» на том, как создается.
Рассмотрим тот же самый пример с автомобилями. Для создания автомобиля нам теперь понадобится специальный класс CarBuilder.
class CarBuilder
{
Car* mCar;
virtual Car* _createNewCar() const {return Car();}
public:
Car* getCar() const {return mCar;}
void createNewCar() {mCar = _createNewCar();}
virtual void addEngine() = 0;
virtual void addWheels() = 0;
...
};
Необходимо также создать класс Director, управленец, в котором будет реализован алгоритм сборки автомобиля. Этот класс не создает конкретных автомобилей, а использует для этого CarBuilder. Его задача – воплотить общий алгоритм конструирования автомобиля, тогда как детали должны быть реализованы в потомках класса CarBuilder. Таким образом достигается важная задача: общий алгоритм создания автомобиля остается неизменным, но при этом конкретный автомобиль может создаваться по сколь угодно сложной схеме, так как CarDirector делегирует CarBuilder всю работу по созданию конкретного автомобиля.
class CarDirector
{
сarBuilder* mBuilder;
public:
void setCarBuilder(CarBuilder* builder)
{
mBuilder = builder;
}
Car* getCar() const
{
return mBuilder->getCar();
}
void constructCar()
{
mBuilder-createNewCar();
mBuilder->addEngine();
mBuilder->addWheels();
...
}
};
Итак, теперь у нас есть класс CarDirector, который задает правила создания автомобилей, и класс CarBuilder, потомки которого создают конкретные детали автомобиля по правилам, описанным в CarDirector. Например, для сборки BMW5 нам понадобится новый класс, порожденный от CarBuilder (см. листинг 4).
Листинг 4
class BMW5CarBuilder: public CarBuilder
{
public:
virtual void addEngine()
{
Engine* engine = new EngineBMW5();
engine->setSpeedLimit(250);
engine->addOil(10);
getCar()->setEngine(engine);
}
virtual void addWheels()
{
getCar()->setFrontWheels(new WheelNokian(16), new WheelNokian(16));
getCar()->setBackWheels(new WheelBridestone(16), new WheelBridestone(16));
}
...
};
Обратим внимание на отличие в конструировании объектов от паттерна «Абстрактная фабрика». Класс BMW5CarBuilder имеет больше возможностей конструирования, чем BMW5CarFactory, поскольку он нацелен на процесс сборки. А BMW5CarFactory просто создает детали и не может влиять на процесс конструирования. В этом основное различие между ними.
Главная проблема этого паттерна та же, что и у «Абстрактной фабрики» – сложно вносить изменения в систему, если она уже наполнена конкретными классами, так как приходится изменять их все.
Достоинства паттерна «Строитель» проявляются при создании сложных и больших иерархий объектов, поскольку у конкретных классов-строителей (потомков CarBuilder) есть возможность влиять на алгоритм конструирования объекта. Например, если когда-либо придется создавать автомобиль без двигателя, то метод addEngine для его разработчика просто будет пуст, тогда как логика работы CarDirector сохранится, поскольку все изменения будут локализованы в новом классе—потомке CarBuilder.
«Прототип»
Паттерн Prototype обычно применяется, когда можно заранее создать объекты всех нужных типов, просто найти среди них подходящий и клонировать его, т.?е. создавать новый объект – копию эталонного. Второе применение – это операции, аналогичные копированию-вставке (Ctrl-C, Ctrl-V). Например, если пользователь «выделил» автомобиль, скопировал его и хочет создать его дубликат – проще всего использовать данный паттерн и клонировать выделенный объект.
Для клонирования объектов нужно добавить в базовый класс метод клонирования, который обычно называют clone. Продолжим пример с автомобилями – расширим интерфейс класса Car (листинг 5).
Листинг 5
class Car
{
public:
virtual Car* clone() const = 0;
virtual const char* getName() const = 0;
virtual void setEngine(Engine* engine);
virtual Engine* getEngine() const;
virtual void setWheels(Wheel* FR, Wheel* FL, Wheel* BR, Wheel* BL);
virtual void setFrontWheels(Wheel* FR, Wheel* FL);
virtual void setBackWheels(Wheel* BR, Wheel* BL);
...
};
Теперь в потомках класса Car нужно перезагрузить и реализовать этот метод, он должен создавать абсолютно идентичную новую копию объекта (см. листинг 6).
Листинг 6
class CarBMW5: public Car
{
public:
virtual Car* clone() const
{
CarBMW5* car = new CarBMW5();
car->setEngine(getEngine()->clone());
car->setFrontWheels(getWheelFR()->clone(), getWheelFL()->clone());
car->setBackWheels(getWheelBR()->clone(), getWheelBL()->clone());
...
return car;
}
...
};
Обратите внимание, что функция клонирования CarBMW5::clone() вызывает функции клонирования для всех своих составных частей. Это стандартная практика: если использовать функцию клонирования в составном объекте, то нужно добавить ее во все объекты, которые он содержит. Таким образом, автомобиль легко клонирует себя, так как просто последовательно запускает процесс копирования всех составных частей и собирает из них новый автомобиль – свою полную копию.
Применений у этого паттерна может быть несколько, как уже было сказано выше. В случае с копированием объектов (Ctrl-C, Ctrl-V) мы будем иметь массив выделенных объектов и при нажатии на Ctrl-C просто создадим их копии, используя метод clone. А нажав на Ctrl-V, мы вставляем эти объекты в нужное место. Мы также можем заранее создать все возможные объекты (типы автомобилей) и поместить их в один массив. А потом легко создавать автомобили по имени: находим объект с нужным именем в массиве и вызываем его метод clone(). Простой пример.
class CarFactory
{
std::vector
public:
CarCreator()
{
mCarTemplates.push_back(new CarBMW5());
mCarTemplates.push_back(new CarToyotaHiace());
mCarTemplates.push_back(new CarLadaKalina());
}
Car* create(const char* name)
{
for(size_t i = 0; i < mCarTemplates.size(); ++i)
if (strcmp(mCarTemplates[i]->getName(), name) == 0)
return mCarTemplates[i]->clone();
return NULL;
}
};