Skip to content
This repository was archived by the owner on Apr 6, 2025. It is now read-only.

ООП Лекция 14. Паттерны поведения.

Vladislav Mansurov edited this page Jun 5, 2022 · 12 revisions

Шаблоны и паттерны поведения

Паттернов поведения колоссальное количество:

Стратегия или Политика (Strategy)

Что такое стратегия? Стратегию можно определить, как выбор подхода решению к чему-либо, какой-либо задачи. В самом названии понятно, что мы задаем стратегию, а потом когда-то будем выполнять данную стратегию решения задачи. Исторически, как он появился иначе, в процедурных языках у нас подход Callback вызовов, так называемый указатель на функцию. По причине чего могли менять алгоритм. Например, у нас функция сортировки, и одном из его параметров передаем указатель на функцию сравнения чего-либо, тем самым меняем алгоритм сравнения в алгоритме сортировки.

В данном случае, язык C++ ООП и поэтому мы функцию оборачиваем в класс, по существу Стратегия - это класс, содержащий один метод. Сама стратегия рассматривается, как абстрактное понятие, то есть создаем конкретную Стратегию от базового класса Стратегии.

Идея

Нам во время выполнения надо менять реализацию какого-либо метода. Мы можем делать производные классы с разными реализациями и осуществлять "миграцию" между классами во врем выполнения - это неудобно, ибо мы начинаем работать с конкретными типами (классами)

Диаграмма

image

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

Рассмотрим сравнение этого паттерна с Структурными:

Во время выполнения программы мы можем менять стратегию. Чтобы не плодить иерархию, мы можем вынести что-то в стратегию, тем самым это паттерн немного схож с декоратором. Но большое сходство он имеет с паттерном Мост, и является вырожденным Мостом. Реализация не всего, а лишь какого-то метода. При этом стратегию могут использовать объекты не являются родственными.

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

Проблемы:

  • алгоритмы "Стратегии" могут иметь пересечение (опять дублирование кода) => решение разбивать стратегию, делать несколько стратегий, можно создавать композиция стратегий, то есть использовать не одну стратегию, а сразу несколько;
  • "Стратегия" должна работать с полями данных объекта. А мы не всегда можем свести к базовым данным.

Пример 01. Стратегия (Strategy).

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

# include <iostream>
# include <memory>
# include <vector>

using namespace std;

class Strategy
{
public:
	virtual ~Strategy() = default;

	virtual void algorithm() = 0;
};

class ConStrategy1 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 1;" << endl; }
};

class ConStrategy2 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 2;" << endl; }
};

class Context
{
protected:
	unique_ptr<Strategy> strategy;

public:
	explicit Context(unique_ptr<Strategy> ptr = make_unique<ConStrategy1>())
		: strategy(move(ptr)) {}
	virtual ~Context() = default;

	virtual void algorithmStrategy() = 0;  
};

class Client1 : public Context
{

public:
	using Context::Context;

	virtual void algorithmStrategy() override  { strategy->algorithm(); }
};

void main()
{
//	shared_ptr<Context> obj = make_shared<Client1>();
	shared_ptr<Context> obj = make_shared<Client1>(make_unique<ConStrategy2>());

	obj->algorithmStrategy();
}

Пример 02. Стратегия (Strategy).

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

# include <iostream>
# include <memory>
# include <vector>

using namespace std;

class Strategy
{
public:
	virtual ~Strategy() = default;

	virtual void algorithm() = 0;
};

class ConStrategy1 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 1;" << endl; }
};

class ConStrategy2 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 2;" << endl; }
};

class Context
{
public:
	virtual void algorithmStrategy(shared_ptr<Strategy> strategy) = 0;
};

class Client1 : public Context
{
public:
	virtual void algorithmStrategy(shared_ptr<Strategy> strategy = make_shared<ConStrategy1>()) override
	{ strategy->algorithm(); }
};

void main()
{
	shared_ptr<Context> obj = make_shared<Client1>();
	shared_ptr<Strategy> strategy = make_shared<ConStrategy2>();

	obj->algorithmStrategy(strategy);
}

Пример 03. Стратегия (Strategy). Стратегия на шаблоне.

Вариант со статическим полиморфизмом. Статический полиморфизм - на этапе компиляции происходит связывание, не можем выбрать на этапе выполнения.

Единственный плюс этого варианта - быстрее.

# include <iostream>
# include <memory>
# include <vector>

using namespace std;

class Strategy
{
public:
	virtual ~Strategy() = default;

	virtual void algorithm() = 0;
};

class ConStrategy1 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 1;" << endl; }
};

class ConStrategy2 : public Strategy
{
public:
	virtual void algorithm() override { cout << "Algorithm 2;" << endl; }
};

template <typename TStrategy = ConStrategy1>
class Context
{
private:
	unique_ptr<TStrategy> strategy;

public:
	Context() : strategy(make_unique<TStrategy>()) {}

	void algorithmStrategy() { strategy->algorithm(); }
};

int main()
{
//	shared_ptr<Context<>> obj = make_shared<Context<>>();
	using Client = Context<ConStrategy2>;
	shared_ptr<Client> obj = make_shared<Client>();

	obj->algorithmStrategy();
}

Пример 04. Стратегия (Strategy) на примере сортировки массива.

# include <iostream>
# include <memory>
# include <initializer_list>

using namespace std;

class Strategy;

class Array
{
public:
	Array(initializer_list<double> list);

	void sort(shared_ptr<Strategy> algorithm);

	const double& operator [](int index) const { return this->arr[index]; }
	unsigned size() const { return count; }

private:
	shared_ptr<double[]> arr;
	unsigned count;
};

class Strategy
{
public:
	virtual void algorithmSort(shared_ptr<double[]> ar, unsigned cnt) = 0;
};

#pragma region Array methods
Array::Array(initializer_list<double> list)
{
	this->count = list.size();
	this->arr = shared_ptr<double[]>(new double[this->count]);

	unsigned i = 0;
	for (auto elem : list)
		arr[i++] = elem;
}

void Array::sort(shared_ptr<Strategy> algorithm)
{
	algorithm->algorithmSort(this->arr, this->count);
}
#pragma endregion


template <typename TComparison>
class BustStrategy : public Strategy
{
public:
	virtual void algorithmSort(shared_ptr<double[]> ar, unsigned cnt) override
	{
		for (int i = 0; i < cnt - 1; i++)
			for (int j = i + 1; j < cnt; j++)
			{
				if (TComparison::compare(ar[i], ar[j]) > 0)
					swap(ar[i], ar[j]);
			}
	}

};

template <typename Type>
class Comparison
{
public:
	static int compare(const Type& elem1, const Type& elem2) { return elem1 - elem2; }
};

ostream& operator <<(ostream& os, const Array& ar)
{
	for (int i = 0; i < ar.size(); i++)
		os << " " << ar[i];
	return os;
}

void main()
{
	using TStrategy = BustStrategy<Comparison<double>>;
	shared_ptr<Strategy> strategy = make_shared<TStrategy>();

	Array ar{ 8., 6., 4., 3., 2., 7., 1. };

	ar.sort(strategy);

	cout << ar << endl;
}

Clone this wiki locally