-
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);
}Строится список из обработчиков. Запрос не связан с обработчиком. Обработчики принимают запрос и смотрят, могут ли они обработать его. Запрос может жить до тех пор, пока он не будет обработан.
Запрос может быть обработан несколькими обработчиками. Запрос может модифицироваться, обработчик может его обработать изменить и послать его дальше по цепочке. Например, ОС Windows, по нажатию мышки идет обработчик этого клика, и каждый обработчик смотрит его это поле видимости или нет, так при нажатие на окне Canvas, то там что отрисовывается, а на поля меню нет.
Cоздать цепочку обработчиков.

Hadler определяет общий интерфейс и. задает механизм передачи запроса, а каждый ConHandler1..n содержат код обработки запросов.
Когда надо использовать?
- один и тот же запрос может выполняться разным способами
- есть четкая последовательность в обработчиках (можем передавать до тех пор, пока не обрабатывается, но важна последовательность)
- на этапе выполнения мы решаем, какие объекты будут в этой цепочке
- Позволяет передавать запрос последовательно по цепочке обработчиков, каждый обработчик сам решает, передавать дальше или нет.
- Новый обработчик добавляется всегда в голову. Не всегда хорошо. Лучше держать обработчики в списке, была возможно передачи и легкой переменчивости.
- В случае изменение объекта, хотелось наблюдать за изменением объекта и реагировать на него (запрос другого рода => запрос на изменение)
# include <iostream>
# include <initializer_list>
# include <memory>
using namespace std;
class AbstractHandler
{
using PtrAbstractHandler = shared_ptr<AbstractHandler>;
protected:
PtrAbstractHandler next;
virtual bool run() = 0;
public:
using Default = shared_ptr<AbstractHandler>;
virtual ~AbstractHandler() = default;
virtual bool handle() = 0;
void add(PtrAbstractHandler node);
void add(PtrAbstractHandler node1, PtrAbstractHandler node2, ...);
void add(initializer_list<PtrAbstractHandler> list);
};
class ConHandler : public AbstractHandler
{
private:
bool condition{ false };
protected:
virtual bool run() override { cout << "Method run;" << endl; return true; }
public:
ConHandler() : ConHandler(false) {}
ConHandler(bool c) : condition(c) { cout << "Constructor;" << endl; }
virtual ~ConHandler() override { cout << "Destructor;" << endl; }
virtual bool handle() override
{
if (!condition) return next ? next->handle() : false;
return run();
}
};
#pragma region Methods
void AbstractHandler::add(PtrAbstractHandler node)
{
if (next)
next->add(node);
else
next = node;
}
void AbstractHandler::add(PtrAbstractHandler node1, PtrAbstractHandler node2, ...)
{
for (Default* ptr = &node1; *ptr; ++ptr)
add(*ptr);
}
void AbstractHandler::add(initializer_list<PtrAbstractHandler> list)
{
for (auto elem : list)
add(elem);
}
#pragma endregion
int main()
{
shared_ptr<AbstractHandler> chain = make_shared<ConHandler>();
chain->add(
{
make_shared<ConHandler>(false),
make_shared<ConHandler>(true),
make_shared<ConHandler>(true),
AbstractHandler::Default()
}
);
cout << "Result = " << chain->handle() << ";" << endl;
}Часто нам надо какой-либо запрос передавать не одному, многим объектам, и это должно выполняться на этапе выполнения.
Возложить на объект, который изменяется функция оповещения других объектов.
# include <iostream>
# include <memory>
# include <vector>
using namespace std;
class Subscriber;
using Reseiver = Subscriber;
class Publisher
{
using Action = void(Reseiver::*)();
using Pair = pair<shared_ptr<Reseiver>, Action>;
private:
vector<Pair> callback;
int indexOf(shared_ptr<Reseiver> r);
public:
bool subscribe(shared_ptr<Reseiver> r, Action a);
bool unsubscribe(shared_ptr<Reseiver> r);
void run();
};
class Subscriber
{
public:
virtual ~Subscriber() = default;
virtual void method() = 0;
};
class ConSubscriber : public Subscriber
{
public:
virtual void method() override { cout << "method;" << endl; }
};
#pragma region Methods Publisher
bool Publisher::subscribe(shared_ptr<Reseiver> r, Action a)
{
if (indexOf(r) != -1) return false;
Pair pr(r, a);
callback.push_back(pr);
return true;
}
bool Publisher::unsubscribe(shared_ptr<Reseiver> r)
{
int pos = indexOf(r);
if (pos != -1)
callback.erase(callback.begin() + pos);
return pos != -1;
}
void Publisher::run()
{
cout << "Run:" << endl;
for (auto elem : callback)
((*elem.first).*(elem.second))();
}
int Publisher::indexOf(shared_ptr<Reseiver> r)
{
int i = 0;
for (auto it = callback.begin(); it != callback.end() && r != (*it).first; i++, ++it);
return i < callback.size() ? i : -1;
}
#pragma endregion
int main()
{
shared_ptr<Subscriber> subscriber1 = make_shared<ConSubscriber>();
shared_ptr<Subscriber> subscriber2 = make_shared<ConSubscriber>();
shared_ptr<Publisher> publisher = make_shared<Publisher>();
publisher->subscribe(subscriber1, &Subscriber::method);
if (publisher->subscribe(subscriber2, &Subscriber::method))
publisher->unsubscribe(subscriber1);
publisher->run();
}