-
Notifications
You must be signed in to change notification settings - Fork 2
ООП Лекция 12. Порождающие паттерны.
Отличие шаблонов от паттернов: шаблон - конкретная реализация чего-либо, а паттерн - шаблон для решения какой-то задачи (как может решаться данная задача). Паттерн мы всегда адаптируем к своей задаче.
- Мы имеем готовое решение
- Засчет готового решения - нюансы все выявлены => надежный код
- Повышается скорость разработки
- Повышается читаемость кода
- Улучшается взаимодействие с коллегами
В случаях использования паттернов они могут быть как структурные, так и паттерны поведеня.
Главная идея: Разделяй и властвуй.
- Задача: создание объектов,
- Проблема: замещение: при использовании полиморфизма, мы работаем с указателями или ссылками на базовое понятие, возникает проблема с созданием объекта (не может быть полиморфных конструкторов).
Полиморфизм дает очень мощный механизм - возможности изменять программу, т.е. добавляя новые классы, не изменяя старый.
A* p = new B; (а что если модифицировать, искать где создаем - проблема)
// избавиться от new
// заменить на
A* p = cr->create();
Разнести на две задачи:
- Принятие решения, какой объект создавать.
- Создание объекта, причем при создании объекта нужно "отвязаться" от конкретного типа.
Мы будем создавать специальные объекты, которые отвечают за порождение других объектов.
- Класс Creator, который будет порождать объекты. И и с помощью наследования будем создавать такие классы для других конкретных объектов.
- Должен быть полиморфным - класс Creator
- Метод Create должен возвращать нам наш объект - в данном случае продукт
- Возникает вопрос, а где мы будем создавать conCreator, опять использовать new? И вот этот момент важен, и тут нужно выделять того кто принимает решение какой объект создавать => Solution
Примечание: Задача Creator создавать нам объекта, чтобы избавиться от new.
# 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()); // здесь есть new, но мы его запрятали, но лучше make_new - а так это безобразие
}
};
#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; }
};
#pragma endregion
int main()
{
shared_ptr<Creator> cr(new ConCreator<ConProd1>()); \\
shared_ptr<Product> ptr = cr->createProduct();
ptr->run();
}
Основная задача этого паттерна - очистить код от оператора new
.
Мы принимаем решения о том, какой объект создавать, во время выполнения программы (мы можем в время выполнения программы менять своё решение, во время работы может меняться поведение системы);
# include <iostream>
# include <memory>
using namespace std;
#pragma region Product
class Product
{
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
private:
int count;
double price;
public:
ConProd1(int c, double p) : count(c), price(p)
{
cout << "Calling the ConProd1 constructor;" << endl;
}
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
template <typename Tbase, typename ...Args>
class BaseCreator
{
public:
virtual ~BaseCreator() = default;
virtual unique_ptr<Tbase> create(Args ...args) = 0;
};
template <typename Tbase, typename Tprod, typename ...Args>
class Creator : public BaseCreator<Tbase, Args...>
{
public:
virtual unique_ptr<Tbase> create(Args ...args) override
{
return unique_ptr<Tbase>(new Tprod(args...));
}
};
using TbaseCreator = BaseCreator<Product, int, double>;
class User
{
public:
void use(shared_ptr<TbaseCreator>& cr)
{
shared_ptr<Product> ptr = cr->create(1, 100.);
ptr->run();
}
};
int main()
{
shared_ptr<TbaseCreator> cr(new Creator<Product, ConProd1, int, double>());
unique_ptr<User> us = make_unique<User>();
us->use(cr);
}
Один раз создав продукт, в дальнейшем мы его можем только возвращать.
# 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());
}
};
#pragma region Method Creator
shared_ptr<Product> Creator::getProduct()
{
if (!product)
{
product = createProduct();
}
return product;
}
#pragma endregion
#pragma region Product
class Product
{
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
public:
ConProd1() { cout << "Calling the ConProd1 constructor;" << endl; }
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
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 создает нам нужный Creator.
Solution предоставляет метод для регистрации creator-ов.
В одном месте принимаем решение, в другом - создаем объект. Отвязываем код от создания конкретных объектов.
На основе чего он может принять решение? Solution должен быть независим от реализации, от конкретного набора классов.
Идея: мы создаем карту продуктов, которые у нас существуют, при добавлении класса регистрируем новый продукт в этой карте, далее, используя эту карту, осуществляем выбор, а в остальном используем банальный поиск.
Solution предоставляет метод для регистрации (в данном случае) Creator'ов. В данном случае для карты - map, состоящий из пар (pair): ключ + значение. Таким образом мы избавились от конструкции switch.
# include <iostream>
# include <initializer_list>
# 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() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
public:
ConProd1() { cout << "Calling the ConProd1 constructor;" << endl; }
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
class ConProd2 : public Product
{
public:
ConProd2() { cout << "Calling the ConProd2 constructor;" << endl; }
virtual ~ConProd2() override { cout << "Calling the ConProd2 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
class CrCreator
{
public:
template <typename Tprod>
static unique_ptr<Creator> createConCreator()
{
return unique_ptr<Creator>(new ConCreator<Tprod>());
}
};
class Solution
{
using CreateCreator = unique_ptr<Creator>(*)();
using CallBackMap = map<size_t, CreateCreator>;
public:
Solution() = default;
Solution(initializer_list<pair<size_t, CreateCreator>> list);
bool registration(size_t id, CreateCreator createfun);
bool check(size_t id) { return callbacks.erase(id) == 1; }
unique_ptr<Creator> create(size_t id);
private:
CallBackMap callbacks;
};
#pragma region Solution
Solution::Solution(initializer_list<pair<size_t, CreateCreator>> list)
{
for (auto elem : list)
this->registration(elem.first, elem.second);
}
bool Solution::registration(size_t id, CreateCreator createfun)
{
return callbacks.insert(CallBackMap::value_type(id, createfun)).second;
}
unique_ptr<Creator> Solution::create(size_t id)
{
CallBackMap::const_iterator it = callbacks.find(id);
if (it == callbacks.end())
{
// throw IdError();
}
return unique_ptr<Creator>((it->second)());
}
#pragma endregion
int main()
{
shared_ptr<Solution> solution(new Solution({ {1, &CrCreator::createConCreator<ConProd1>} }));
solution->registration(2, &CrCreator::createConCreator<ConProd2>);
shared_ptr<Creator> cr(solution->create(2));
shared_ptr<Product> ptr = cr->createProduct();
ptr->run();
}
# include <iostream>
# include <memory>
# include <map>
using namespace std;
class Product;
class AbstractCreator
{
public:
virtual ~AbstractCreator() = default;
virtual unique_ptr<Product> createProduct() = 0;
};
template <typename Tprod>
class Creator : public AbstractCreator
{
public:
Creator() { cout << "Calling the Creator constructor;" << endl; }
virtual ~Creator() override { cout << "Calling the Creator destructor;" << endl; }
virtual unique_ptr<Product> createProduct() override
{
return make_unique<Tprod>();
}
};
#pragma region Product
class Product
{
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
public:
ConProd1() { cout << "Calling the ConProd1 constructor;" << endl; }
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
class ConProd2 : public Product
{
public:
ConProd2() { cout << "Calling the ConProd2 constructor;" << endl; }
virtual ~ConProd2() override { cout << "Calling the ConProd2 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
class Solution
{
using CallBackMap = map<size_t, shared_ptr<AbstractCreator>> ;
public:
Solution() = default;
template <typename Tprod>
bool registration(size_t id);
bool check(size_t id) { return callbacks.erase(id) == 1; }
shared_ptr<AbstractCreator> create(size_t id);
private:
CallBackMap callbacks{};
};
#pragma region Solution
template <typename Tprod>
bool Solution::registration(size_t id)
{
return callbacks.emplace(id, make_shared<Creator<Tprod>>()).second;
}
shared_ptr<AbstractCreator> Solution::create(size_t id)
{
CallBackMap::const_iterator it = callbacks.find(id);
if (it == callbacks.end())
{
// throw IdError();
}
return it->second;
}
#pragma endregion
int main()
{
unique_ptr<Solution> solution = make_unique<Solution>();
if (solution->registration<ConProd1>(1))
solution->registration<ConProd2>(2);
shared_ptr<AbstractCreator> cr(solution->create(2));
shared_ptr<Product> ptr = cr->createProduct();
ptr->run();
}
# include <iostream>
# include <memory>
using namespace std;
class Product;
template <typename Tprod>
class Creator
{
public:
unique_ptr<Product> createProduct()
{
return unique_ptr<Product>(new Tprod());
}
};
#pragma region Product
class Product
{
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
public:
ConProd1() { cout << "Calling the ConProd1 constructor;" << endl; }
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
class User
{
public:
template<typename Tprod>
void use(shared_ptr<Creator<Tprod>> cr);
};
template<typename Tprod>
void User::use(shared_ptr<Creator<Tprod>> cr)
{
shared_ptr<Product> ptr = cr->createProduct();
ptr->run();
}
int main()
{
shared_ptr<Creator<ConProd1>> cr(new Creator<ConProd1>());
unique_ptr<User> us = make_unique<User>();
us->use(cr);
}
# include <iostream>
# include <memory>
using namespace std;
#pragma region Product
class Product
{
public:
virtual ~Product() = default;
virtual void run() = 0;
};
class ConProd1 : public Product
{
public:
ConProd1() { cout << "Calling the ConProd1 constructor;" << endl; }
virtual ~ConProd1() override { cout << "Calling the ConProd1 destructor;" << endl; }
virtual void run() override { cout << "Calling the run method;" << endl; }
};
#pragma endregion
template <typename Tcrt>
class Creator
{
public:
auto create() const
{
return static_cast<const Tcrt*>(this)->create_impl();
}
};
template <typename Tprod>
class ProductCreator : public Creator<ProductCreator<Tprod>>
{
public:
unique_ptr<Product> create_impl() const
{
return unique_ptr<Product>(new Tprod());
}
};
class Work
{
public:
template <typename Type>
static auto create(const Type& crt)
{
return crt.create();
}
};
void main()
{
Creator<ProductCreator<ConProd1>> cr;
auto product = Work::create(cr);
product->run();
}
Абстрактная фабрика - развитие фабричного метода с добавлением функционала
- Задача: создание "семейства" разных объектов, но связанных между собой
- Можно "плодить" разные ветви Cretor-ов под каждый тип. продуктов, но мы теряем связь между этими продуктами.
Возникает необходимость создания объектов, связанных между собой или единой библиотекой, или единым приложением (например, подменить графическую библиотеку Qt
на какую-нибудь другую)
Чтобы:
- не плодить кучу Creator;
- ввести контроль над подменой.
Идея: объединить в одном классе Creator-ы для разных иерархий.
Пример: в графических библиотеках: кисточка, ручка, сцена и т. п.
Каждая конкретная фабрика будет отвечать за создание определенного семейства объектов, т.е. под конкретную библиотеку будет создавать своя фабрика.
- Преимущества фабричного метода + не нужно контролировать создание объектов из разных семейств
- Облегчение чтение кода, несмотря на размерность кода
- Абстрактная фабрика накладывает требования на продукты (в разных семействах должны быть представлены все продукты, которые определяет базовая абстрактная фабрика)
- Каждый графический приметив надо обернуть в свой класс. Увеличивается количество кода из-за кучи таких обёрток.
- Проблема: Абстрактная фабрика должна давать нам базовый интерфейс, в случае если библиотеки имеют разные понятие, а вот написание базового для них - затруднительно.
# 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));
// но лучше
// return make_unique<QtGraphics>(im);
}
virtual unique_ptr<BasePen> createPen(shared_ptr<Color> cl)
{
return unique_ptr<BasePen>(new QtPen());
// но лучше
// return make_unique<QtPen>();
}
virtual unique_ptr<BaseBrush> createBrush(shared_ptr<Color> cl)
{
return unique_ptr<BaseBrush>(new QtBrush());
// но лучше
// return make_unique<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()));
// лучше не использовать new
// shared_ptr<AbstractGraphFactory> grfactory = make_shared<QtGraphFactory>();
// shared_ptr<Image> image = make_shared<Image>();
// shared_ptr<BaseGraphics> graphics1 = grfactory->createGraphics(image);
// shared_ptr<BaseGraphics> graphics2 = grfactory->createGraphics(image);
}
Чтобы породить какой-то объект в него на передать и Creator и Factory => тащить в Factory Creator. А если в методе уже есть подобный объект, а зачем тогда тащить фабричный метод, если уже в этом месте работаем с объектом.
Идея: Если мы в данном месте или в данном методе работаем с каким-то объектом и нам нужно создать подобный объект, но при этом не тащить Creator в Factory, а создать новый объект на основе существующегося, т.е. создать подобный объект или копию объекта, не зная класса объекта (так как мы работаем с указателями или ссылками, чтобы не было switch).
Мы добавляем в базовый класс метод clone()
, возвращающий указатель на себя, производные классы реализуют clone()
под себя, возвращая указатель на подобный объект.
# include <iostream>
# include <memory>
using namespace std;
class BaseObject
{
public:
virtual ~BaseObject() = default;
virtual unique_ptr<BaseObject> clone() = 0;
};
class Object1 : public BaseObject
{
public:
Object1() { cout << "Default constructor;" << endl; }
Object1(const Object1& obj) { cout << "Copy constructor;" << endl; }
~Object1() { cout << "Destructor;" << endl; }
virtual unique_ptr<BaseObject> clone() override
{
return unique_ptr<BaseObject>(new Object1(*this));
// но лучше
// return make_unique<Object1>(*this)
}
};
int main()
{
unique_ptr<BaseObject> ptr1(new Object1());
// shared_ptr<BaseObject> ptr1 = make_shared<Object1>();
auto ptr2 = ptr1->clone();
}
Примечание: unique легко преобразовать в shared указатель.
В любом месте, работая с указателем на базовый класс, можем получить копию нужного нам объекта
Но все таки у нас остались проблемы, в случае если у нас сложный объект, возникает вопрос как создать или возникает потребность создавать некоторые элементы поэтапно.
Задача: создать объекты, отвечающие за создание сложных объектов (создаются поэтапно)
Пример: сложного объекта: 3D-модель из 1 лр.
Строитель - класс, включающий в себя этапы создания сложного объекта.
Также нужен отдельный класс для контролирования создания сложного объекта.
Идея: в один класс свести этапы создания какого-либо объекта.
Builder создает объект, Director контролирует создание (подготавливает данные для создания и отдает объект) - разделение ответственности.
Агрегация на уровне базовых классов необязательна. Дело в том, что объект один и тот же, но строится может по-разному.
Когда надо использовать? Для поэтапного создания сложных объектов. Когда создание объекта разнесено в коде, объект создается не сразу (например, данные подготавливаются поэтапно)
Преимущество: вынесение создания и контроля в отдельные классы
Проблема: с данными - конкретные строители базируются на одних и тех же данных (чтобы могли подменить один объект одного строителя другим)
# include <iostream>
# include <memory>
using namespace std;
class Product
{
public:
public:
Product() { cout << "Calling the ConProd1 constructor;" << endl; }
~Product() { cout << "Calling the ConProd1 destructor;" << endl; }
void run() { cout << "Calling the run method;" << 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>();
}
};
#pragma region Methods
shared_ptr<Product> Builder::getProduct()
{
if (!product) { product = createProduct(); }
return product;
}
shared_ptr<Product> ConBuilder::createProduct()
{
if (part == 2) { product = make_shared<Product>(); }
return product;
}
#pragma endregion
int main()
{
shared_ptr<Builder> builder = make_shared<ConBuilder>();
shared_ptr<Director> director = make_shared<Director>();
shared_ptr<Product> prod = director->create(builder);
if (prod)
prod->run();
}
Когда надо использовать? При возникновении задач, когда гарантированно должен быть создан только 1 объект какого-либо класса - создать только один раз.
Идея: убрать конструкторы из
public
-части, то есть сделать так, чтобы извне нельзя было создать объект. Также объект должен создаваться только 1: сделать статический метод, пораждающий объект при необходимости.
# include <iostream>
# include <memory>
using namespace std;
class Product
{
public:
static shared_ptr<Product> instance()
{
static shared_ptr<Product> myInstance(new Product());
return myInstance;
}
~Product() { cout << "Destructor;" << endl; }
void f() { cout << "Method f;" << endl; }
Product(const Product&) = delete; // запрещаем
Product& operator=(const Product&) = delete; // запрещаем
private:
Product() { cout << "Default constructor;" << endl; }
};
int main()
{
shared_ptr<Product> ptr(Product::instance());
ptr->f();
}
Запрещаем конструктор копирования (и оператор присваивания)
# include <iostream>
# include <memory>
using namespace std;
template <typename Type>
class Singleton
{
public:
static Type& instance()
{
static unique_ptr<Type> myInstance(new Type());
return *myInstance;
}
Singleton() = delete;
Singleton(const Singleton<Type>&) = delete;
Singleton<Type>& operator=(const Singleton<Type>&) = delete;
};
class Product
{
public:
Product() { cout << "Default constructor;" << endl; }
~Product() { cout << "Destructor;" << endl; }
void f() { cout << "Method f;" << endl; }
};
int main()
{
Product& d = Singleton<Product>::instance(1, 2.);
d.f();
}
Создается конкретного типа в отличие от примера 11.
Проблема: не накладываем ограничения на объект (может иметь конструкторы), но если мы работаем через эту оболочку, это гарантирует то, что этот объект только 1.
Недостатки: этот шаблон называют "антипаттерн", мы как бы создаем глобальный объект (доступ через глобальный интерфейс, вызовом статического метода), второй недостаток - проблема с подменой на этапе выполнения.
Атернатива: фабричный метод, который создает объект только один раз
Примечание: Одиночку лучше НЕ ИСПОЛЬЗОВАТЬ, отдавать предпочтение фабричному методу
Пул объектов предоставляет набор готовых объектов, которые мы можем использовать
Пример использования: Многопроцессорная система. Количество заданий, которое мы должны создать, должно быть не больше количества процессоров. Чтобы каждый процессор выполнял свою задачу.
Когда создание или уничтожение какого-либо объекта - трудоемкий процесс и надо "держать" определенное количество объектов в системе.
- Он держит эти объекты.
- Может их создавать (то есть может расширяться).
- По запросу отдает объект.
- Если клиенту этот объект не нужен, он может его вернуть в пул.
- Исходя из пунктов 3 и 4, для каждого включенного объекта в пул мы должны установить, используется он или не используется.
Диаграмма крайне простая:
Клиент может принимать объект и возвращать его в пул объектов.
Пример 13. Пул объектов (Object Pool). Реализован на основе одиночки.
# include <iostream>
# include <memory>
# include <iterator>
# include <vector>
using namespace std;
class Product
{
private:
static size_t count;
public:
Product() { cout << "Constructor(" << ++count << ");" << endl; }
~Product() { cout << "Destructor(" << count-- << ");" << endl; }
void clear() { cout << "Method clear: 0x" << this << endl; }
};
size_t Product::count = 0;
template <typename Type>
class ObjectPool
{
public:
static shared_ptr<ObjectPool<Type>> instance(); // статический метод из одиночки
shared_ptr<Type> getObject();
bool releaseObject(shared_ptr<Type>& obj);
size_t count() const { return pool.size(); }
iterator<output_iterator_tag, const pair<bool, shared_ptr<Type>>> begin() const;
iterator<output_iterator_tag, const pair<bool, shared_ptr<Type>>> end() const;
ObjectPool(const ObjectPool<Type>&) = delete;
ObjectPool<Type>& operator=(const ObjectPool<Type>&) = delete;
private:
vector<pair<bool, shared_ptr<Type>>> pool;
ObjectPool() {}
pair<bool, shared_ptr<Type>> create();
template <typename Type>
friend ostream& operator << (ostream& os, const ObjectPool<Type>& pl);
};
#pragma region ObjectPool class Methods
template <typename Type>
shared_ptr<ObjectPool<Type>> ObjectPool<Type>::instance()
{
static shared_ptr<ObjectPool<Type>> myInstance(new ObjectPool<Type>());
return myInstance;
}
template <typename Type>
shared_ptr<Type> ObjectPool<Type>::getObject()
{
size_t i;
for (i = 0; i < pool.size() && pool[i].first; ++i);
if (i < pool.size())
{
pool[i].first = true;
}
else
{
pool.push_back(create());
}
return pool[i].second;
}
template <typename Type>
bool ObjectPool<Type>::releaseObject(shared_ptr<Type>& obj)
{
size_t i;
for (i = 0; i < pool.size() && pool[i].second != obj; ++i);
if (i == pool.size()) return false;
obj.reset();
pool[i].first = false;
pool[i].second->clear();
return true;
}
template <typename Type>
pair<bool, shared_ptr<Type>> ObjectPool<Type>::create()
{
return pair<bool, shared_ptr<Type>>(true, shared_ptr<Type>(new Type()));
}
#pragma endregion
template <typename Type>
ostream& operator << (ostream& os, const ObjectPool<Type>& pl)
{
for (auto elem : pl.pool)
os << "{" << elem.first << ", 0x" << elem.second << "} ";
return os;
}
int main()
{
shared_ptr<ObjectPool<Product>> pool = ObjectPool<Product>::instance();
vector<shared_ptr<Product>> vec(4);
for (auto& elem : vec)
elem = pool->getObject();
pool->releaseObject(vec[1]);
cout << *pool << endl;
shared_ptr<Product> ptr = pool->getObject();
vec[1] = pool->getObject();
cout << *pool << endl;
}
После использования объекта мы возвращаем его в пул, и здесь возможна так называемая утечка информации. Мы работали с этим объектом. Вернув его в пул, он находится в том состоянии, с которым мы с ним перед этим работали. Его надо либо вернуть в исходное состояние, либо очистить, чтобы при отдаче этого объекта другому клиенту не произошла утечка информации.
Пару комментариев
- Пулл объектов - контейнерный класс, удобно использовать итераторы!
- Необходимо знать, занят объект или нет, используем пару: ключ (bool) и объект.