-
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;
}Часто нам надо какой-либо запрос передавать не одному, многим объектам, и это должно выполняться на этапе выполнения.
Возложить на объект, который изменяется функция оповещения других объектов.
Но возникает вопрос, он должен знать кого оповещать. Значит на тех кто хочет наблюдать, они должны подписаться на эти изменения, то есть издатель должен держать список тех, кто подписался на изменения с ним. Если есть метод подписать, то должен быть и метод отписаться.
Обычно, когда важно наличие подписчиков, издатель держит указатель (shared) на подписчика. Если же не важно наличие подписчиков, издатель держит указатель (weak) на подписчика.

- Издатели не зависят от подписчиков
- Схема гибкая: можно как подписаться, так и подписаться (надо делать iterator)
- Паттерн тяжёлый. Мы должны держать список подписчиков;
- Подписка друг на друга.
# 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();
}