Skip to content

ООП Лекция 12. Порождающие паттерны.

Vladislav Mansurov edited this page May 9, 2022 · 4 revisions

Общие сведения о паттернах

Отличие шаблонов от паттернов: шаблон - конкретная реализация чего-либо, а паттерн - шаблон для решения какой-то задачи (как может решаться данная задача). Паттерн мы всегда адаптируем к своей задаче.

Преимущества:

  • Мы имеем готовое решение
  • Засчет готового решения - нюансы все выявлены => надежный код
  • Повышается скорость разработки
  • Повышается читаемость кода
  • Улучшается взаимодействие с коллегами

В случаях использования паттернов они могут быть как структурные, так и паттерны поведеня.

Главная идея: Разделяй и властвуй.

Порождающие паттерны

  • Задача: создание объектов,
  • Проблема: замещение: при использовании полиморфизма, мы работаем с указателями или ссылками на базовое понятие, возникает проблема с созданием объекта (не может быть полиморфных конструкторов).

Полиморфизм дает очень мощный механизм - возможности изменять программу, т.е. добавляя новые классы, не изменяя старый.

image

Фабричный метод (Factory Method или Factory)

A* p = new B; (а что если модифицировать, искать где создаем - проблема)
// избавиться от new
// заменить на
A* p = cr->create();

Идея

Разнести на две задачи:

  1. Принятие решения, какой объект создавать.
  2. Создание объекта, причем при создании объекта нужно "отвязаться" от конкретного типа.

Сreator

Мы будем создавать специальные объекты, которые отвечают за порождение других объектов.

  • Класс Creator, который будет порождать объекты. И и с помощью наследования будем создавать такие классы для других конкретных объектов.
    • Должен быть полиморфным - класс Creator
    • Метод Create должен возвращать нам наш объект - в данном случае продукт
  • Возникает вопрос, а где мы будем создавать conCreator, опять использовать new? И вот этот момент важен, и тут нужно выделять того кто принимает решение какой объект создавать => Solution

Диаграмма Сreator и Продукта

image

Примечание: Задача Creator создавать нам объекта, чтобы избавиться от new.

Пример 1 Фабричный метод (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()); // здесь есть 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.

Мы принимаем решения о том, какой объект создавать, во время выполнения программы (мы можем в время выполнения программы менять своё решение, во время работы может меняться поведение системы);

Пример 2. Фабричный метод (Factory Method). Шаблонный базовый класс.

# 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);
}

Пример 3. Фабричный метод (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());
	}
};

#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

Принятие решения о том, кто будет создавать объект, мы выносим. Класс, принимающий решение, объект не создает. Он создает объект для его создания, то есть Solution создает нам нужный Creator.

Диаграмма Solution, Creator и Product

image

Solution предоставляет метод для регистрации creator-ов.

В одном месте принимаем решение, в другом - создаем объект. Отвязываем код от создания конкретных объектов.

На основе чего он может принять решение? Solution должен быть независим от реализации, от конкретного набора классов.

Идея: мы создаем карту продуктов, которые у нас существуют, при добавлении класса регистрируем новый продукт в этой карте, далее, используя эту карту, осуществляем выбор, а в остальном используем банальный поиск.

Пример 4 Фабричный метод (Factory Method). Разделение обязанностей.

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();
}

Пример 5. Фабричный метод (Factory Method). Разделение обязанностей.

# 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();
}

Пример 6. Шаблонный фабричный метод. Подмена с перекомпиляцией.

# 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);
}

Пример 7. Фабричный метод (Factory Method). «Статический полиморфизм» (CRTP).

# 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();
}

Абстрактная Фабрика (Abstract Factory)

Абстрактная фабрика - развитие фабричного метода с добавлением функционала

  • Задача: создание "семейства" разных объектов, но связанных между собой
  • Можно "плодить" разные ветви Cretor-ов под каждый тип. продуктов, но мы теряем связь между этими продуктами.

Диаграмма к примеру Абстрактной Фабрики

image

Возникает необходимость создания объектов, связанных между собой или единой библиотекой, или единым приложением (например, подменить графическую библиотеку Qt на какую-нибудь другую)

Чтобы:

  • не плодить кучу Creator;
  • ввести контроль над подменой.

Идея: объединить в одном классе Creator-ы для разных иерархий.

Пример: в графических библиотеках: кисточка, ручка, сцена и т. п.

Каждая конкретная фабрика будет отвечать за создание определенного семейства объектов, т.е. под конкретную библиотеку будет создавать своя фабрика.

Преимущества:

  • Преимущества фабричного метода + не нужно контролировать создание объектов из разных семейств
  • Облегчение чтение кода, несмотря на размерность кода

Недостатки:

  • Абстрактная фабрика накладывает требования на продукты (в разных семействах должны быть представлены все продукты, которые определяет базовая абстрактная фабрика)
  • Каждый графический приметив надо обернуть в свой класс. Увеличивается количество кода из-за кучи таких обёрток.
  • Проблема: Абстрактная фабрика должна давать нам базовый интерфейс, в случае если библиотеки имеют разные понятие, а вот написание базового для них - затруднительно.

Пример 8. Абстрактная фабрика (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));
         // но лучше
         // 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. А если в методе уже есть подобный объект, а зачем тогда тащить фабричный метод, если уже в этом месте работаем с объектом.

Прототип (Prototype)

Идея: Если мы в данном месте или в данном методе работаем с каким-то объектом и нам нужно создать подобный объект, но при этом не тащить Creator в Factory, а создать новый объект на основе существующегося, т.е. создать подобный объект или копию объекта, не зная класса объекта (так как мы работаем с указателями или ссылками, чтобы не было switch).

Диаграмма

image

Мы добавляем в базовый класс метод clone(), возвращающий указатель на себя, производные классы реализуют clone() под себя, возвращая указатель на подобный объект.

Пример 9. Прототип (Prototype).

# 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 указатель.

В любом месте, работая с указателем на базовый класс, можем получить копию нужного нам объекта

Строитель (Builder)

Но все таки у нас остались проблемы, в случае если у нас сложный объект, возникает вопрос как создать или возникает потребность создавать некоторые элементы поэтапно.

Задача: создать объекты, отвечающие за создание сложных объектов (создаются поэтапно)

Пример: сложного объекта: 3D-модель из 1 лр.

Строитель - класс, включающий в себя этапы создания сложного объекта.

Также нужен отдельный класс для контролирования создания сложного объекта.

Идея: в один класс свести этапы создания какого-либо объекта.

Builder создает объект, Director контролирует создание (подготавливает данные для создания и отдает объект) - разделение ответственности.

image

Агрегация на уровне базовых классов необязательна. Дело в том, что объект один и тот же, но строится может по-разному.

Когда надо использовать? Для поэтапного создания сложных объектов. Когда создание объекта разнесено в коде, объект создается не сразу (например, данные подготавливаются поэтапно)

Преимущество: вынесение создания и контроля в отдельные классы

Проблема: с данными - конкретные строители базируются на одних и тех же данных (чтобы могли подменить один объект одного строителя другим)

Пример 10. Строитель (Builder)

# 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();
}

Одиночка (Singleton) - Анти-паттерн

Когда надо использовать? При возникновении задач, когда гарантированно должен быть создан только 1 объект какого-либо класса - создать только один раз.

Идея: убрать конструкторы из public-части, то есть сделать так, чтобы извне нельзя было создать объект. Также объект должен создаваться только 1: сделать статический метод, пораждающий объект при необходимости.

Пример 11. Одиночка (Singleton).

# 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();
}

Запрещаем конструктор копирования (и оператор присваивания)

Пример 12. Шаблон одиночка (Singleton).

# 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.

Недостатки: этот шаблон называют "антипаттерн", мы как бы создаем глобальный объект (доступ через глобальный интерфейс, вызовом статического метода), второй недостаток - проблема с подменой на этапе выполнения.

Атернатива: фабричный метод, который создает объект только один раз

Примечание: Одиночку лучше НЕ ИСПОЛЬЗОВАТЬ, отдавать предпочтение фабричному методу

Пул объектов (Object Pull)

Пул объектов предоставляет набор готовых объектов, которые мы можем использовать

Пример использования: Многопроцессорная система. Количество заданий, которое мы должны создать, должно быть не больше количества процессоров. Чтобы каждый процессор выполнял свою задачу.

Когда надо использовать?

Когда создание или уничтожение какого-либо объекта - трудоемкий процесс и надо "держать" определенное количество объектов в системе.

Задачи

  • Он держит эти объекты.
  • Может их создавать (то есть может расширяться).
  • По запросу отдает объект.
  • Если клиенту этот объект не нужен, он может его вернуть в пул.
  • Исходя из пунктов 3 и 4, для каждого включенного объекта в пул мы должны установить, используется он или не используется.

Диаграмма

Диаграмма крайне простая: image

Клиент может принимать объект и возвращать его в пул объектов.

Пример 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) и объект.
Clone this wiki locally