Порождающие паттерны: одиночка, фабричный метод, абстрактная фабрика, строитель, прототип, пул объектов. - Painted-Black/BMSTU-OOP GitHub Wiki
Порождающие паттерны проектирования абстрагируют процесс инстанцирования. Они помогают сделать систему независимой от способа создания, композиции и представления объектов.
Предоставляет интерфейс для создания семейств взаимосвязанных или взаимозависимых объектов, не специфицируя их конкретных классов.
Используйте паттерн абстрактная фабрика, когда:
- система не должна зависеть от того, как создаются, компонуются и представляются входящие в нее объекты;
- входящие в семейство взаимосвязанные объекты должны использоваться вместе и вам необходимо обеспечить выполнение этого органичения;
- система должна конфигурироваться одним из семейств составляющих ее объектов;
- вы хотите предоставить библиотеку объектов, раскрывая только их интерфейсы, но не реализацию.
- AbstractFactory - абстрактная фабрика
- объявляет интерфейс для операций, создающих абстрактные объекты-продукты
- ConcreteFactory - конкретная фабрика
- реализует операции, создающие конкретные объекты-продукты
- AbstractProduct - абстрактный продукт
- объявляет интерфейс для типа объекта-продукта
- ConcreteProduct - конкретный продукт
- определяет объект-продукт, создаваемый соответствующей конкретной фабрикой
- реализует интерфейс AbstractProduct
- Client - клиент
- пользуется исключительно интерфейсами, которые объявлены в классах AbstractFactory и AbstractProduct.
Обычно во время выполнения создается единственный экземпляр класса ConcreteFactory. Эта конкретная фабрика создает объекты-продукты, имеющие вполне определенную реализацию. Для создания других видов объектов клиент должен воспользоваться другой конкретной фабрикой.
AbstractFactory передоверяет создание объектов-продуктов своему подклассу ConcreteFactory.
Патерн абстрактная фабрика имеет следующие плюсы и минусы:
- изолирует конкретные классы
- упрощает замену семейств продуктов
- гарантирует сочетаемость продуктов
- поддержать новый вид продуктов трудно.
Отделяет конструирование сложного объекта от его представления, так что в результате одного и того же процесса конструирования могут получаться разные представления.
Используйте паттерн строитель, когда:
- алгоритм создания сложного объекта не должен зависеть от того, из каких частей состоит объект и как они стыкуются между собой
- процесс конструирования объекта должен обеспечивать различные представления конструируемого объекта
- Builder
- задает абстрактный интерфейс для создания частей объекта Product
- ConcreteBuilder
- конструирует и собирает вместе части продукта посредством реализации интерфейса Builder
- определяет создаваемое представление и следит за ним
- предоставляет интерфейс для доступа к продукту
- Director - распорядитель
- конструирует объект, пользуясь интерфейсом Builder
- Product
- представляет сложный конструируемый объект. ConcreteBuilder строит внетреннее представление продукта и определяет процесс его сборки
- включает классы, которые определяют составные части, в том числе интерфейсы для сборки конечного результата из частей.
Клиент создает объект-распорядитель Director и конфигурирует его нужным объектом-строителем Builder. Распорядитель уведомляет строителя о том, что нужно построить очередную часть продукта. Строитель обрабатывает запросы распорядителя и добавляет новые части к продукту. Клиент забирает продукт у строителя.
Плюсы и минусы паттерна строитель:
- позволяет изменять внутреннее представление продукта
- изолирует код, реализующий конструирование и представление
- дает более тонкий контроль над процессом конструирования
- усложняет код программы из-за введения дополнительных классов
- клиент будет привязан к конкретным классам строителей, так как в интерфейсе строителя может не быть метода получения результата.
Определяет интерфейс для создания объекта, но оставляет подклассам решение о том, какой класс инстанцировать. Фабричный метод позволяет классу делегировать инстанцирование подклассам.
Используйте паттерн фабричный метод, когда:
- классу заранее неизвестно, объекты каких классов ему нужно создавать
- класс спроектирован так, чтобы объекты, которые он создает, специфицировались подклассами
- класс делегирует свои обязанности одному из нескольких вспомогательных подклассов, и вы планируете локализовать знание о том, какой класс принимает эти обязанности на себя.
- Product
- определяет интерфейс объектов, создаваемых фабричным методом
- ConcreteProduct
- реализует интерфейс Product
- Creator
- объявляет фабричный метод, возвращающий объекты типа Product. Creator может также определять реалзацию по умолчанию фабричноно метода, который возвращает объект ConcreteProduct
- может вызывать фабричный метод для создания объекита Product
- ConcreteCreator
- замещает фабричный метод, возвращающий объект ConcreteProduct.
Создатель "полагается" на свои подклассы в определении фабричного метода, который будет возвращать экземпляр подходящего конкретного продукта.
Фабричные методы избавляют проектировщика от необходимости встраивать в код зависящие от приложения классы. Код имеет дело только с интерфейсом класса Product, поэтому он может работать с любыми определенными пользователями классами конкретных продуктов.
class Creator
{
public:
virtual unique_ptr<Product> create() = 0;
};
template <typename Tprod>
class ConCreator: public Creator
{
virtual unique_ptr<Product> create() override;
};
unique_ptr<Product> ConCreator<TProd>::create()
{
return unique_ptr(new Tprod());
}
//...
ConCreator<ConProd> cr;
//...
unique_ptr<Product> ptr = create();
Задает виды создаваемых объектов с помощью экземпляра-прототипа и создает новые объекты путем копирования этого прототипа.
Используйте паттерн прототим, когда система не должна зависеть от того, как в ней создаются, компонуются и представляются продукты:
- инстанцируемые классы определяются во время выполнения
- для того чтобы избежать простроения иерархий классов или фабрик, параллельных иерархии классов продуктов
- экземпляры класса могут находиться в одном из не очень большого числа состояний.
- Prototype
- обяляет интерфейс для клонирования самого себя
- ConcretePrototype
- реализует операцию клонирования себя
- Client
- создает новый объект, обращаясь к прототипу с запросом клонировать себя.
Клиент обращается к прототипу, чтобы тот создал свою копию.
Преимущества:
- добавление и удаление продуктов во время выполения
- спецификация новых объектов путем изменения значений
- специфицирование новых объектов путем изменения структуры
- уменьшение числа подклассов
- динамическое конфигурирование приложения классами.
Гарантирует, что у класса есть только один экземпляр, и предоставляет к нему глобальную точку доступа.
Используйте паттер одиночка, когда:
- должен быть ровно один экземпляр некоторого класса, лего доступный всем клиентам
- единственный экземпляр должен расширяться путем порождения подклассов,и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода.
- Singleton
- определяет операцию Instance, которая позволяет клиентам получать доступ к единственноу экземпляру. Instance - это операция класса
- может нести ответственность за содание собственного уникального экземпляра.
Клиенты получают доступ к экземпляру Singleton только через операцию Instance.
Достоинства:
-
контролирует доступ к единственному экземпляру
-
уменьшение числа имен (позволяет избежать засорения пространства имен глобальными переменными)
-
допускает уточнение операций и представления
-
допускает переменное число экземпляров
-
большая гибкость, чем у операций класса
template class Singleton { public: static Type& instance() { if (MyInstance == nullptr) MyInstance = new Type(); return MyInstance; } Singleton() = delete; private: static Type* MyInstance = nullptr; };
Недостатки:
- глобальный объект
- мы не можем управлять созданием и удалением объекта.
Альтернатива: Creator с get.
Модификация Creator
template <typename Type>
class ObjectPool
{
public:
shared_ptr<Type> create();
vector<shared_ptr<Type>> vec;
};
template <typename Type>
shared_ptr<Type> ObjectPool<Type>::get()
{
bool key = false;
size_t i = 0;
while (i < vec.size() && !key)
{
if (vec[i].use_count() == 0)
key = true;
else
i++;
}
return key ? vec[i].create();
}