-
Notifications
You must be signed in to change notification settings - Fork 0
ООП 12. Порождающие паттерны: фабричный метод (Factory Method), абстрактная фабрика (Abstract Factory), строитель (Builder). Их преимущества и недостатки.
Отличие шаблонов от паттернов: шаблон - конкретная реализация чего-либо, а паттерн - шаблон для решения какой-то задачи (как может решаться данная задача). Паттерн мы всегда адаптируем к своей задаче.
- Мы имеем готовое решение
- За счет готового решения - нюансы все выявлены => надежный код
- Повышается скорость разработки
- Повышается читаемость кода
- Улучшается взаимодействие с коллегами (достаточно сказать название паттерна, который вы используете, и всё сразу станет понятно)
Задача порождающего паттерна: создание объектов
Проблема: не может быть полиморфных конструкторов. Если внутри метода мы работаем со ссылкой/указателем на базовое понятие, будет вызван конструктор базового понятия.
Идея
Разнести на две задачи:
- Принятие решения, какой объект создавать.
- Создание объекта, причем при создании объекта нужно "отвязаться" от конкретного типа.
Мы будем создавать специальные объекты, которые отвечают за порождение других объектов.
Диаграмма
Использование
Когда нам нужно использовать фабричный метод:
- Основная задача: подмена одного объекта на другой.
- Когда принятие решение в одном месте кода, создание - в другом.
Преимущества
-
Облегчается добавление новых классов, избавляем методы от привязки к конкретным классам.
-
Код очищается от
new
, используя полиморфизм по полной (создаём новые классы, не изменяя уже написанный код) -
Паттерн работает во всех языках
-
Позволяет разнести в коде принятие решения о создании объекта (solution) и само создание (creator)
-
Решение о том, какой объект создавать, принимается во время выполнения, тогда же можно менять это решение
-
Пример. Фабричный метод (Factory Method). Новый объект.
#include <iostream> #include <memory> using namespace std; class Product; class Creator { public: virtual unique_ptr<Product> createProduct() = 0; }; template <typename Tprod> class ConCreator : public Creator { public: virtual unique_ptr<Product> createProduct() override { return unique_ptr<Product>(new Tprod()); } }; class Product { public: virtual ~Product() = 0; virtual void run() = 0; }; Product::~Product() {} class ConProd1 : public Product { public: virtual ~ConProd1() override { cout << "Destructor;" << endl; } virtual void run() override { cout << "Method run;" << endl; } }; #pragma endregion int main() { shared_ptr<Creator> cr(new ConCreator<ConProd1>()); shared_ptr<Product> ptr = cr->createProduct(); ptr->run(); }
-
Пример. Фабричный метод (Factory Method). Без повторного создания.
# include <iostream> # include <memory> using namespace std; class Product; class Creator { public: shared_ptr<Product> getProduct(); protected: virtual shared_ptr<Product> createProduct() = 0; private: shared_ptr<Product> product; }; template <typename Tprod> class ConCreator : public Creator { protected: virtual shared_ptr<Product> createProduct() override { return shared_ptr<Product>(new Tprod()); } }; shared_ptr<Product> Creator::getProduct() { if (!product) { product = createProduct(); } return product; } class Product { public: virtual ~Product() = 0; virtual void run() = 0; }; Product::~Product() {} class ConProd1 : public Product { public: virtual ~ConProd1() override { cout << "Destructor;" << endl; } virtual void run() override { cout << "Method run;" << endl; } }; int main() { shared_ptr<Creator> cr(new ConCreator<ConProd1>()); shared_ptr<Product> ptr1 = cr->getProduct(); shared_ptr<Product> ptr2 = cr->getProduct(); cout << ptr1.use_count() << endl; ptr1->run(); }
-
Solution
Принятие решения о том, кто будет создавать объект, мы выносим. Класс, принимающий решение, объект не создает. Он создает объект для его создания.
Solution предоставляет метод для регистрации creator-ов.
На основе чего Solution может принять решение, какой класс создавать? Solution должен быть независим от реализации, от конкретного набора классов - следовательно, **мы не можем использовать switch-case.**
**Идея решения:**
создаем карту продуктов, которые у нас существуют. При добавлении нового класса, регистрируем его в этой карте. Используя эту карту, осуществляем выбор. **Solution предоставляет метод для регистрации креаторов классов (в этой карте).**
- Пример. Фабричный метод (Factory Method). Разделение обязанностей.
Solution предоставляет метод для регистрации (в данном случае) Creator'ов. В данном случае для карты - map, состоящий из пар (pair): ключ + значение. Таким образом мы избавились от конструкции switch.
- Код
```cpp
# include <iostream>
# include <memory>
# include <map>
using namespace std;
class Product;
class Creator
{
public:
virtual unique_ptr<Product> createProduct() = 0;
};
template <typename Tprod>
class ConCreator : public Creator
{
public:
virtual unique_ptr<Product> createProduct() override
{
return unique_ptr<Product>(new Tprod());
}
};
#pragma region Product
class Product
{
public:
virtual ~Product() = 0;
virtual void run() = 0;
};
Product::~Product() {}
class ConProd1 : public Product
{
public:
virtual ~ConProd1() override { cout << "Destructor;" << endl; }
virtual void run() override { cout << "Method run;" << endl; }
};
unique_ptr<Creator> createConCreator()
{
return unique_ptr<Creator>(new ConCreator<ConProd1>());
}
class Solution
{
public:
typedef unique_ptr<Creator> (*CreateCreator)();
bool registration(size_t id, CreateCreator createfun)
{
return callbacks.insert(CallBackMap::value_type(id, createfun)).second;
}
bool check(size_t id) { return callbacks.erase(id) == 1; }
unique_ptr<Creator> create(size_t id)
{
CallBackMap::const_iterator it = callbacks.find(id);
if (it == callbacks.end())
{
throw IdError();
}
return unique_ptr<Creator>((it->second)());
}
private:
using CallBackMap = map<size_t, CreateCreator>;
CallBackMap callbacks;
};
int main()
{
Solution solution;
solution.registration(1, createConCreator);
shared_ptr<Creator> cr(solution.create(1));
shared_ptr<Product> ptr = cr->createProduct();
ptr->run();
}
```
Абстрактная фабрика - развитие фабричного метода с добавлением функционала
-
Задача: создание "семейства" разных объектов, но связанных между собой
Можно "плодить" разные ветви Creаtor'ов под каждый тип. продуктов, но мы теряем связь между этими продуктами.
Пример: в графических библиотеках: кисточка, ручка, сцена и т. п.
Каждая конкретная фабрика будет отвечать за создание определенного семейства объектов
Как и для фабричного метода, должен быть solution, который принимает решение, какую фабрику создавать
-
Преимущества: не надо контролировать создание каждого объекта - только всего семейства целиком. Целостность системы.
-
Недостаток: абстрактная фабрика накладывает требования на продукты (в разных семействах должны быть представлены все продукты, которые определяет базовая абстрактная фабрика). Очень сложно привести всё к единому интерфейсу.
-
Пример кода. Абстрактная фабрика (Abstract Factory).
# include <iostream> # include <memory> using namespace std; class Image {}; class Color {}; class BaseGraphics { public: virtual ~BaseGraphics() = 0; }; BaseGraphics::~BaseGraphics() {} class BasePen {}; class BaseBrush {}; class QtGraphics : public BaseGraphics { public: QtGraphics(shared_ptr<Image> im) { cout << "Constructor QtGraphics;" << endl; } virtual ~QtGraphics() override { cout << "Destructor QtGraphics;" << endl; } }; class QtPen : public BasePen {}; class QtBrush : public BaseBrush {}; class AbstractGraphFactory { public: virtual unique_ptr<BaseGraphics> createGraphics(shared_ptr<Image> im) = 0; virtual unique_ptr<BasePen> createPen(shared_ptr<Color> cl) = 0; virtual unique_ptr<BaseBrush> createBrush(shared_ptr<Color> cl) = 0; }; class QtGraphFactory : public AbstractGraphFactory { virtual unique_ptr<BaseGraphics> createGraphics(shared_ptr<Image> im) { return unique_ptr<BaseGraphics>(new QtGraphics(im)); } virtual unique_ptr<BasePen> createPen(shared_ptr<Color> cl) { return unique_ptr<BasePen>(new QtPen()); } virtual unique_ptr<BaseBrush> createBrush(shared_ptr<Color> cl) { return unique_ptr<BaseBrush>(new QtBrush()); } }; int main() { shared_ptr<AbstractGraphFactory> grfactory(new QtGraphFactory()); shared_ptr<BaseGraphics> graphics1 = grfactory->createGraphics(shared_ptr<Image>(new Image())); shared_ptr<BaseGraphics> graphics2 = grfactory->createGraphics(shared_ptr<Image>(new Image())); }
Проблема: сложные объекты создаются поэтапно, иногда - этапы создания разнесены в разных частях программы.
Идея решения: вынести в отдельный код этапы создания сложных объектов.
- Строитель - класс, который включает в себя этапы создания сложного объекта.
- Кроме того, выделяем еще один класс, который контролирует создание - Директор.
- Строитель - создает объект.
- Директор - подготавливает данные для создания, контролирует этапы создания, отдает объект клиенту.
Диаграмма (должен быть ещё производный директор ConDirector):
-
Пример кода. Строитель
# include <iostream> # include <memory> using namespace std; class Product { public: Product() { cout << "Default constructor;" << endl; } ~Product() { cout << "Destructor;" << endl; } void run() { cout << "Method run;" << endl; } }; class Builder { public: virtual bool buildPart1() = 0; virtual bool buildPart2() = 0; shared_ptr<Product> getProduct(); protected: virtual shared_ptr<Product> createProduct() = 0; shared_ptr<Product> product; }; class ConBuilder : public Builder { public: virtual bool buildPart1() override { cout << "Completed part: " << ++part << ";" << endl; return true; } virtual bool buildPart2() override { cout << "Completed part: " << ++part << ";" << endl; return true; } protected: virtual shared_ptr<Product> createProduct() override; private: size_t part{0}; }; class Director { public: shared_ptr<Product> create(shared_ptr<Builder> builder) { if (builder->buildPart1() && builder->buildPart2()) return builder->getProduct(); return shared_ptr<Product>(); } }; shared_ptr<Product> Builder::getProduct() { if (!product) { product = createProduct(); } return product; } shared_ptr<Product> ConBuilder::createProduct() { if (part == 2) { product = shared_ptr<Product>(new Product()); } return product; } int main() { shared_ptr<Builder> builder(new ConBuilder()); shared_ptr<Director> director(new Director()); shared_ptr<Product> prod = director->create(builder); if (prod) prod->run(); }
Как и для фабричного метода, должен быть solution, который принимает решение, какого директора создавать
- Когда надо использовать?
- Для поэтапного создания сложных объектов
- Когда создание объекта разнесено в коде, объект создается не сразу (например, данные подготавливаются поэтапно)
- Преимущество: вынесение создания и контроля в отдельные классы
- Проблема: с данными - конкретные строители базируются на одних и тех же входных данных и количество этапов строительства не меняется ⇒ возникнут проблемы при подмене одного строителя на другой).
- Решение: сделать агрегацию не на уровне базовых классов, а на уровне производных. Тогда можно будет менять интерфейс базового билдера (в том числе расширять или сужать), и конкретный директор будет работать с конкретным билдером не как с базовым классом, а как с производным.