Паттерн Строитель на C++
Строитель – это порождающий паттерн проектирования, который позволяет создавать объекты пошагово.
В отличие от других порождающих паттернов, Строитель позволяет производить различные продукты, используя один и тот же процесс строительства.
Особенности паттерна на C++
Сложность:
Популярность:
Применимость: Паттерн можно часто встретить в C++ коде, особенно там, где требуется пошаговое создание продуктов или конфигурация сложных объектов.
Признаки применения паттерна: Строителя можно узнать в классе, который имеет один создающий метод и несколько методов настройки создаваемого продукта. Обычно, методы настройки вызывают для удобства цепочкой (например, someBuilder->setValueA(1)->setValueB(2)->create()
).
Концептуальный пример
Этот пример показывает структуру паттерна Строитель, а именно – из каких классов он состоит, какие роли эти классы выполняют, и как они взаимодействуют друг с другом.
main.cc: Пример структуры паттерна
/**
* Имеет смысл использовать паттерн Строитель только тогда, когда ваши продукты
* достаточно сложны и требуют обширной конфигурации.
*
* В отличие от других порождающих паттернов, различные конкретные строители
* могут производить несвязанные продукты. Другими словами, результаты различных
* строителей могут не всегда следовать одному и тому же интерфейсу.
*/
class Product1{
public:
std::vector<std::string> parts_;
void ListParts()const{
std::cout << "Product parts: ";
for (size_t i=0;i<parts_.size();i++){
if(parts_[i]== parts_.back()){
std::cout << parts_[i];
}else{
std::cout << parts_[i] << ", ";
}
}
std::cout << "\n\n";
}
};
/**
* Интерфейс Строителя объявляет создающие методы для различных частей объектов
* Продуктов.
*/
class Builder{
public:
virtual ~Builder(){}
virtual void ProducePartA() const =0;
virtual void ProducePartB() const =0;
virtual void ProducePartC() const =0;
};
/**
* Классы Конкретного Строителя следуют интерфейсу Строителя и предоставляют
* конкретные реализации шагов построения. Ваша программа может иметь несколько
* вариантов Строителей, реализованных по-разному.
*/
class ConcreteBuilder1 : public Builder{
private:
Product1* product;
/**
* Новый экземпляр строителя должен содержать пустой объект продукта,
* который используется в дальнейшей сборке.
*/
public:
ConcreteBuilder1(){
this->Reset();
}
~ConcreteBuilder1(){
delete product;
}
void Reset(){
this->product= new Product1();
}
/**
* Все этапы производства работают с одним и тем же экземпляром продукта.
*/
void ProducePartA()const override{
this->product->parts_.push_back("PartA1");
}
void ProducePartB()const override{
this->product->parts_.push_back("PartB1");
}
void ProducePartC()const override{
this->product->parts_.push_back("PartC1");
}
/**
* Конкретные Строители должны предоставить свои собственные методы
* получения результатов. Это связано с тем, что различные типы строителей
* могут создавать совершенно разные продукты с разными интерфейсами.
* Поэтому такие методы не могут быть объявлены в базовом интерфейсе
* Строителя (по крайней мере, в статически типизированном языке
* программирования). Обратите внимание, что PHP является динамически
* типизированным языком, и этот метод может быть в базовом интерфейсе.
* Однако мы не будем объявлять его здесь для ясности.
*
* Как правило, после возвращения конечного результата клиенту, экземпляр
* строителя должен быть готов к началу производства следующего продукта.
* Поэтому обычной практикой является вызов метода сброса в конце тела
* метода getProduct. Однако такое поведение не является обязательным, вы
* можете заставить своих строителей ждать явного запроса на сброс из кода
* клиента, прежде чем избавиться от предыдущего результата.
*/
/**
* Будьте осторожны с владением памятью. Когда вы вызываете GetProduct,
* пользователь этой функции несет ответственность за освобождение этой
* памяти. Здесь может быть лучше использовать умные указатели, чтобы
* избежать утечек памяти.
*/
Product1* GetProduct() {
Product1* result= this->product;
this->Reset();
return result;
}
};
/**
* Директор отвечает только за выполнение шагов построения в определённой
* последовательности. Это полезно при производстве продуктов в определённом
* порядке или особой конфигурации. Строго говоря, класс Директор необязателен,
* так как клиент может напрямую управлять строителями.
*/
class Director{
/**
* @var Builder
*/
private:
Builder* builder;
/**
* Директор работает с любым экземпляром строителя, который передаётся ему
* клиентским кодом. Таким образом, клиентский код может изменить конечный
* тип вновь собираемого продукта.
*/
public:
void set_builder(Builder* builder){
this->builder=builder;
}
/**
* Директор может строить несколько вариаций продукта, используя одинаковые
* шаги построения.
*/
void BuildMinimalViableProduct(){
this->builder->ProducePartA();
}
void BuildFullFeaturedProduct(){
this->builder->ProducePartA();
this->builder->ProducePartB();
this->builder->ProducePartC();
}
};
/**
* Клиентский код создаёт объект-строитель, передаёт его директору, а затем
* инициирует процесс построения. Конечный результат извлекается из объекта-
* строителя.
*/
/**
* Я здесь использовал простые указатели, но вы можете
* предпочесть использовать умные указатели
*/
void ClientCode(Director& director)
{
ConcreteBuilder1* builder = new ConcreteBuilder1();
director.set_builder(builder);
std::cout << "Standard basic product:\n";
director.BuildMinimalViableProduct();
Product1* p= builder->GetProduct();
p->ListParts();
delete p;
std::cout << "Standard full featured product:\n";
director.BuildFullFeaturedProduct();
p= builder->GetProduct();
p->ListParts();
delete p;
// Помните, что паттерн Строитель можно использовать без класса Директор.
std::cout << "Custom product:\n";
builder->ProducePartA();
builder->ProducePartC();
p=builder->GetProduct();
p->ListParts();
delete p;
delete builder;
}
int main(){
Director* director= new Director();
ClientCode(*director);
delete director;
return 0;
}
Output.txt: Результат выполнения
Standard basic product:
Product parts: PartA1
Standard full featured product:
Product parts: PartA1, PartB1, PartC1
Custom product:
Product parts: PartA1, PartC1