-
Notifications
You must be signed in to change notification settings - Fork 2
ООП Лекция 14. Паттерны поведения.
Паттернов поведения колоссальное количество:
Что такое стратегия? Стратегию можно определить, как выбор подхода решению к чему-либо, какой-либо задачи. В самом названии понятно, что мы задаем стратегию, а потом когда-то будем выполнять данную стратегию решения задачи. Исторически, как он появился иначе, в процедурных языках у нас подход Callback вызовов, так называемый указатель на функцию. По причине чего могли менять алгоритм. Например, у нас функция сортировки, и одном из его параметров передаем указатель на функцию сравнения чего-либо, тем самым меняем алгоритм сравнения в алгоритме сортировки.
В данном случае, язык C++ ООП и поэтому мы функцию оборачиваем в класс, по существу Стратегия - это класс, содержащий один метод. Сама стратегия рассматривается, как абстрактное понятие, то есть создаем конкретную Стратегию от базового класса Стратегии.
Нам во время выполнения надо менять реализацию какого-либо метода. Мы можем делать производные классы с разными реализациями и осуществлять "миграцию" между классами во врем выполнения - это неудобно, ибо мы начинаем работать с конкретными типами (классами)

Клиент может установить для нашего класса конкретную стратегию (алгоритм) и, работая с объектом, он будет вызывать этот конкретный алгоритм. Во время работы мы можем этот алгоритм поменять.
Рассмотрим сравнение этого паттерна с Структурными:
Во время выполнения программы мы можем менять стратегию.
Чтобы не плодить иерархию, мы можем вынести что-то в стратегию, тем самым это паттерн немного схож с декоратором. Но большое сходство он имеет с паттерном Мост, и является вырожденным Мостом. Реализация не всего, а лишь какого-то метода. При этом стратегию могут использовать объекты не являются родственными.
Проблема адаптера - пересечение интерфейсов и решаем дублированием кода, когда Стратегия эту проблему решает без дублирования кода, то есть можно ее использовать с адаптером, тем самым решаем проблему пересечения интерфейсом без дублирования кода.
- алгоритмы "Стратегии" могут иметь пересечение (опять дублирование кода) => решение разбивать стратегию, делать несколько стратегий, можно создавать композиция стратегий, то есть использовать не одну стратегию, а сразу несколько;
- "Стратегия" должна работать с полями данных объекта. А мы не всегда можем свести к базовым данным.
Объект держит указатель на стратегию. Мы один раз установили стратегию, можем, конечно, её поменять. Вызывая для объекта, вызывается та стратегия, которая нас интересует.
# 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();
}При выполнении мы передаем, какую стратегию хотим использовать. Мы не держим указатель, а устанавливаем при работе.
# 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);
}Вариант со статическим полиморфизмом. Статический полиморфизм - на этапе компиляции происходит связывание, не можем выбрать на этапе выполнения.
Единственный плюс этого варианта - быстрее.
# 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();
}# 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;
}Команда - объект, который держит указатель на объект, указатель на метод, который должен быть вызван для этого объекта и данные, которые нужны для этого метода.
Но хотелось бы отвязаться от объекта. Перенести решение, может ли быть обработан запрос, на объект, который принимает запрос.
Возможны разные запросы (загрузить, повернуть, перенести и подобное). Идея такая: обернуть каждый запрос в отдельный класс (класс может быть как простой, так и составной)
При этом передаем данные для выполнения команды, и передаем их в Callback, при этом жестко завязаны к методу, то есть прелесть можем все скрыть, можем сделать один метод execute, будут передаваться метод по указателю на метод.

- Уменьшается зависимость между объектами, не нужно держать связь
- Команду можем выполнить не сразу, а через время, можно сформировать очередь
- Если добавить к команде композит, то можно формировать сложные команды из нескольких команд
# include <iostream>
# include <memory>
# include <vector>
# include <initializer_list>
using namespace std;
class Command
{
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
template <typename Reseiver>
class SimpleCommand : public Command
{
using Action = void(Reseiver::*)();
using Pair = pair<shared_ptr<Reseiver>, Action>;
private:
Pair call;
public:
SimpleCommand(shared_ptr<Reseiver> r, Action a) : call(r, a) {}
virtual void execute() override { ((*call.first).*call.second)(); }
};
class CompoundCommand : public Command
{
using VectorCommand = vector<shared_ptr<Command>>;
private:
VectorCommand vec;
public:
CompoundCommand(initializer_list<shared_ptr<Command>> lt);
virtual void execute() override;
};
# pragma region Methods
CompoundCommand::CompoundCommand(initializer_list<shared_ptr<Command>> lt)
{
for (auto elem : lt)
vec.push_back(elem);
}
void CompoundCommand::execute()
{
for (auto com : vec)
com->execute();
}
# pragma endregion
class Object
{
public:
void run() { cout << "Run method;" << endl; }
};
int main()
{
shared_ptr<Object> obj = make_shared<Object>();
shared_ptr<Command> command = make_shared<SimpleCommand<Object>>(obj, &Object::run);
command->execute();
shared_ptr<Command> complex(new CompoundCommand
{
make_shared<SimpleCommand<Object>>(obj, &Object::run),
make_shared<SimpleCommand<Object>>(obj, &Object::run)
});
complex->execute();
}# include <iostream>
# include <memory>
using namespace std;
template <typename Reseiver>
class Command
{
public:
virtual ~Command() = default;
virtual void execute(shared_ptr<Reseiver>) = 0;
};
template <typename Reseiver>
class SimpleCommand : public Command<Reseiver>
{
using Action = void(Reseiver::*)();
private:
Action act;
public:
SimpleCommand(Action a) : act(a) {}
virtual void execute(shared_ptr<Reseiver> r) override { ((*r).*act)(); }
};
class Object
{
public:
virtual void run() = 0;
};
class ConObject : public Object
{
public:
virtual void run() override { cout << "Run method;" << endl; }
};
int main()
{
shared_ptr<Command<Object>> command = make_shared<SimpleCommand<Object>>(&Object::run);
shared_ptr<Object> obj = make_shared<ConObject>();
command->execute(obj);
}