-
Notifications
You must be signed in to change notification settings - Fork 0
ООП 14. Паттерны поведения: стратегия (Strategy), команда (Command), цепочка обязанностей (Chain of Responsibility), подписчик издатель (Publish Subscribe), посредник (Mediator). Их преимущества и недостатки.
- Мы имеем готовое решение
- За счет готового решения - нюансы все выявлены => надежный код
- Повышается скорость разработки
- Повышается читаемость кода
- Улучшается взаимодействие с коллегами (достаточно сказать название паттерна, который вы используете, и всё сразу станет понятно)
Идея
Нам во время выполнения надо менять реализацию какого-либо метода. Мы можем делать производные классы с разными реализациями и осуществлять "миграцию" между классами во врем выполнения - это неудобно, ибо мы начинаем работать с конкретными типами (классами)
То, что может меняться (это действие) вынести в отдельный класс, который будет выполнять только это действие. Мы можем во время выполнения подменять одно на другое.
Паттерн Стратегия определяет семейство всевозможных алгоритмов.
Плюсы**:**
- Возможность подмены алгоритма
- Отделение сущности от реализации
- Ограничение разрастания иерархии
- Разъеб(уменьшение) дублирования кода
- Позволяет добавлять функционал классу
Минусы:
- Не всегда можем свести к базовой, реализация может быть разная, а мы хотим подменять, алгоритм базируется на обработке данных, а данные могут быть разные
- Диаграмма(Лек 14_1 14мин):
Клиент может установить для нашего класса конкретную стратегию (алгоритм) и, работая с объектом, он будет вызывать этот конкретный алгоритм. Во время работы мы можем этот алгоритм поменять.
Стратегия это вырожденный паттерн МОСТ, только выносим не всю реализацию, а лишь часть - метод
ПИШЕМ ЛЮБОЙ
-
Пример кода 1 Держим указатель. Стратегия (Strategy).
Объект держит указатель на стратегию. Мы один раз установили стратегию, можем, конечно, её поменять. Вызывая для объекта, вызывается та стратегия, которая нас интересует.
-
Код
# include <iostream> # include <memory> # include <vector> using namespace std; class Strategy { public: virtual ~Strategy() = 0; virtual void algorithm() = 0; }; Strategy::~Strategy() = default; 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 { private: unique_ptr<Strategy> strategy; public: explicit Context(Strategy* ptr) : strategy(ptr) {} void algorithmStrategy() { strategy->algorithm(); } }; int main() { Context obj(new ConStrategy1()); obj.algorithmStrategy(); }
-
-
Пример кода 2 Передаем стратегию. Стратегия (Strategy).
При выполнении мы передаем, какую стратегию хотим использовать. Мы не держим указатель, а устанавливаем при работе.
-
Код
# include <iostream> # include <memory> # include <vector> using namespace std; class Strategy { public: virtual ~Strategy() = 0; virtual void algorithm() = 0; }; Strategy::~Strategy() = default; 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: void algorithmStrategy(shared_ptr<Strategy> strategy) { strategy->algorithm(); } }; int main() { Context obj; obj.algorithmStrategy(shared_ptr<Strategy>(new ConStrategy1())); obj.algorithmStrategy(shared_ptr<Strategy>(new ConStrategy2())); }
-
-
Пример кода 3 Статический полиморфизм. Стратегия (Strategy).
Вариант со статическим полиморфизмом. Статический полиморфизм - на этапе компиляции происходит связывание, не можем выбрать на этапе выполнения.
Единственный плюс этого варианта - быстрее.
-
Код
# include <iostream> # include <memory> # include <vector> using namespace std; class Strategy { public: virtual ~Strategy() = 0; virtual void algorithm() = 0; }; Strategy::~Strategy() = default; 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 CStrategy> class Context { private: unique_ptr<CStrategy> strategy; public: Context() : strategy(new CStrategy()) {} void algorithmStrategy() { strategy->algorithm(); } }; int main() { Context<ConStrategy1> obj; obj.algorithmStrategy(); }
-
Идея
Возможны разные запросы (загрузить, повернуть, перенести и подобное). Идея такая: обернуть каждый запрос в отдельный класс (класс может быть как простой, так и составной). Если мы чётко знаем, кто должен обработать запрос, то надо держать объект (обработчик) и указатель на метод, и их можно передавать тому, кто это обработает.
Диаграмма
Запрос, или команда, идет в виде объекта. Базовый класс - и у нас может быть не одна команда, а несколько, в зависимости от того, что нам нужно. Команда может нести данные, может нести что надо сделать, (указатель на метод какого-либо объекта, например.)
- UML
-
Пример кода. Команда (Command).
# include <iostream> # include <memory> using namespace std; class Command { public: virtual ~Command() = default; virtual void execute() = 0; }; template <typename Reseiver> class SimpleCommand : public Command { private: typedef void(Reseiver::*Action)(); shared_ptr<Reseiver> reseiver; Action action; public: SimpleCommand(shared_ptr<Reseiver> r, Action a) : reseiver(r), action(a) {} virtual void execute() override { ((*reseiver).*action)(); } }; class Object { public: void run() { cout << "Run method;" << endl; } }; int main() { shared_ptr<Object> obj(new Object()); shared_ptr<Command> command(new SimpleCommand<Object>(obj, &Object::run)); command->execute(); }
Преимущества
- Унификация обработки запросов к системе (событий)
- Уменьшается зависимость между объектами, не нужно держать связь
- Команду можем выполнить не сразу, а через время, можно сформировать очередь
- Если добавить к команде композит, то можно формировать сложные команды из нескольких команд
- UML
Часто задача сводится к тому, что запрос должны обрабатывать несколько объектов, а не один.
- Идея: создать цепочку обработчиков.
- Преимущества: позволяет передавать запрос последовательно по цепочке обработчиков, каждый обработчик сам решает, передавать дальше или нет.
- Диаграмма
Handler определяет общий интерфейс и задает механизм передачи запроса, а каждый Handler1..n содержат код обработки запросов
-
Пример кода. Цепочка обязанностей (Chain of Responsibility).
Рекурсивный список
В примерах – идем по цепочке, пока не наткнемся на обработчик с состоянием true, дальше по цепочке не пойдем.
# include <iostream> # include <memory> using namespace std; class AbstractHandler { protected: shared_ptr<AbstractHandler> next; virtual bool run() = 0; public: using Default = shared_ptr<AbstractHandler>; virtual ~AbstractHandler() = default; virtual bool handle() = 0; void add(shared_ptr<AbstractHandler> node); void add(initializer_list<shared_ptr<AbstractHandler>> list); }; class ConHandler : public AbstractHandler { private: bool condition{ false }; protected: virtual bool run() override { cout << "Method run;" << endl; return true; } public: ConHandler() = default; 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(); } }; void AbstractHandler::add(shared_ptr<AbstractHandler> node) { if (next) next->add(node); else next = node; } void AbstractHandler::add(initializer_list<shared_ptr<AbstractHandler>> list) { for (auto elem : list) add(elem); } int main() { shared_ptr<AbstractHandler> chain(new ConHandler()); chain->add({ shared_ptr<AbstractHandler>(new ConHandler(false)), shared_ptr<AbstractHandler>(new ConHandler(true)), shared_ptr<AbstractHandler>(new ConHandler(true)), AbstractHandler::Default()} ); cout << "Result = " << chain->handle() << ";" << endl;; }
-
Когда надо использовать?
- один и тот же запрос может выполняться разными способами
- есть четкая последовательность в обработчиках (можем передавать до тех пор, пока не обработается, но важна последовательность)
- на этапе выполнения мы решаем, какие объекты будут в этой цепочке
Часто нам надо какой-либо запрос передавать не одному, а многим объектам, и это должно выполняться на этапе выполнения
Этот паттерн задает механизм: тот, кто хочет принять сообщение, тот подписывается у издателя (подписался - получил, не подписался - не получил)
Группа объектов реагирует на один объект, на его изменения, издатель оповещает всех подписчиков, когда происходят изменения, вызывая их методы
- Диаграмма (у Publisher еще один метод -
unsubscribe()
)
-
Пример кода. Синхронно
#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(); }
-
Пример кода. Асинхронно.
# include <iostream> # include <string> # include <QObject> using namespace std; class Employer : public QObject // Publisher { Q_OBJECT private: const int day_salary = 15; const int day_advance = 25; public: Employer() = default; void notifyEmployeis(int day); signals: void salaryPayment(); void taskIssuance(); }; # pragma region Method Publisher void Employer::notifyEmployeis(int day) { if (day == day_salary || day == day_advance) { emit salaryPayment(); } else { emit taskIssuance(); } } # pragma endregion enum class Mood { happy, sad }; class Employee : public QObject // Subscribe { Q_OBJECT private: string name; Mood mood; public: Employee(string name) : name(name), mood(Mood::sad) {} string getName() { return name; } Mood getMood() { return mood; } void subscribeOnEmployer(const Employer* ptrEmployer); void unsubscribeFromEmployer(const Employer* ptrEmployer); public slots: void onSalaryPayment() { mood = Mood::happy; } void onTaskIssuance() { mood = Mood::sad; } }; # pragma region Methods Subscribe void Employee::subscribeOnEmployer(const Employer* ptrEmployer) { QObject::connect(ptrEmployer, SIGNAL(salaryPayment()), this, SLOT(onSalaryPayment())); QObject::connect(ptrEmployer, SIGNAL(taskIssuance()), this, SLOT(onTaskIssuance())); } void Employee::unsubscribeFromEmployer(const Employer* ptrEmployer) { QObject::disconnect(ptrEmployer, SIGNAL(salaryPayment()), this, SLOT(onSalaryPayment())); QObject::disconnect(ptrEmployer, SIGNAL(taskIssuance()), this, SLOT(onTaskIssuance())); } # pragma endregion ostream& operator<< (ostream &out, const Employee &employee) { string mood = employee.getMood() == Mood::happy ? "Happy with a lot of money!!!!))))" : "Sad with a lot of work....(((("; return out << "Name: " << employee.getName() << ", Mood: " << mood << endl; } int main() { //Создадим работодателя, который выступает в роли объекта publisher Employer google; //Создадим несколько работников, которые выступают в роли объекта subscriber Employee vanya("Vanya"); Employee artem("Artem"); Employee maxim("Maxim"); Employee dmitrii("Dmitrii"); //Проверим текущее состояние работников cout << "Employees states after creation:" << endl; cout << vanya; cout << artem; cout << maxim; cout << dmitrii << endl; //Подпишем нескольких работников на объект работодателя vanya.subscribeOnEmployer(&google); artem.subscribeOnEmployer(&google); dmitrii.subscribeOnEmployer(&google); //Проверим состояние после подписки на объект работодателя cout << "Employees states after publishing:" << endl; cout << vanya; cout << artem; cout << maxim; cout << dmitrii << endl; //Вызовем метод получения оповещения для всех подписчиков, в котором вызовется сигнал salaryPayment int day_salary = 25; google.notifyEmployeis(day_salary); //Проверим состояние работников, после получения зарплаты cout << "Employees states after salary event:" << endl; cout << vanya; cout << artem; cout << maxim; cout << dmitrii << endl; //Вызовем метод получения оповещения для всех подписчиков, в котором вызовется сигнал taskIssuance int someDay = 10; google.notifyEmployeis(someDay); //Проверим состояние работников, после получения задания cout << "Employees states after task event:" << endl; cout << vanya; cout << artem; cout << maxim; cout << dmitrii << endl; return 0; }
Издатель держит список объектов, которые на него подписались. Вектор пар: подписчик и метод подписчика, который необходимо вызвать. У издателя должны быть методы подписаться() и отписаться().
Отправленный сигнал может обрабатываться подписчиками синхронно и асинхронно.
- Преимущества:
- Издатели не зависят от подписчиков
- Схема гибкая: можно как подписаться, так и отписаться (надо делать iterator)
- Недостатки:
- издатель должен держать список объектов, которые на него подписаны (паттерн тяжёлый)
- нет порядка в оповещении подписчиков
- если издателей много, (и каждый подписчик может быть издателем), связей будет очень много
-
Идея: (последний недостаток подписчика-издателя*(издателей много - много связей)*) связи вынести в отдельный объект, тогда каждый объект будет обращаться к этому отдельному объекту, а он в свою очередь будет искать нужные связи (с кем ему связаться).
-
Позволяет уменьшить количество связей между объектами благодаря перемещению связей в него, в посредник. Так как посредник должен работать со всеми объектами, то он содержит указатели на все эти объекты.
-
Диаграмма
-
Пример кода. Посредник (Mediator)
В данном примере кода будут от левого, отправляться всем правым, а от правого, всем левым
# include <iostream> # include <memory> # include <list> # include <vector> using namespace std; class Message {}; // Request class Mediator; class Colleague { private: weak_ptr<Mediator> mediator; public: virtual ~Colleague() = default; void setMediator(shared_ptr<Mediator> mdr) { mediator = mdr; } virtual bool send(shared_ptr<Message> msg); virtual void receive(shared_ptr<Message> msg) = 0; }; class ColleagueLeft : public Colleague { public: virtual void receive(shared_ptr<Message> msg) override { cout << "Right - > Left;" << endl; } }; class ColleagueRight : public Colleague { public: virtual void receive(shared_ptr<Message> msg) override { cout << "Left - > Right;" << endl; } }; class Mediator { protected: list<shared_ptr<Colleague>> colleagues; public: virtual ~Mediator() = default; virtual bool send(const Colleague* coleague, shared_ptr<Message> msg) = 0; static bool add(shared_ptr<Mediator> mediator, initializer_list<shared_ptr<Colleague>> list); }; class ConMediator : public Mediator { public: virtual bool send(const Colleague* coleague, shared_ptr<Message> msg) override; }; #pragma region Methods Colleague bool Colleague::send(shared_ptr<Message> msg) { shared_ptr<Mediator> mdr = mediator.lock(); return mdr ? mdr->send(this, msg) : false; } #pragma endregion #pragma region Methods Mediator bool Mediator::add(shared_ptr<Mediator> mediator, initializer_list<shared_ptr<Colleague>> list) { if (!mediator || list.size() == 0) return false; for (auto elem : list) { mediator->colleagues.push_back(elem); elem->setMediator(mediator); } return true; } bool ConMediator::send(const Colleague* colleague, shared_ptr<Message> msg) { bool flag = false; for (auto& elem : colleagues) { if (dynamic_cast<const ColleagueLeft*>(colleague) && dynamic_cast<ColleagueRight*>(elem.get())) { elem->receive(msg); flag = true; } else if (dynamic_cast<const ColleagueRight*>(colleague) && dynamic_cast<ColleagueLeft*>(elem.get())) { elem->receive(msg); flag = true; } } return flag; } #pragma endregion int main() { shared_ptr<Mediator> mediator =make_shared<ConMediator>(); shared_ptr<Colleague> col1 = make_shared<ColleagueLeft>(); shared_ptr<Colleague> col2 = make_shared<ColleagueRight>(); shared_ptr<Colleague> col3 = make_shared<ColleagueLeft>(); shared_ptr<Colleague> col4 = make_shared<ColleagueLeft>(); Mediator::add(mediator, { col1, col2, col3, col4 }); shared_ptr<Message> msg = make_shared<Message>(); col1->send(msg); col2->send(msg); } /* Left - > Right; Right - > Left; Right - > Left; Right - > Left; */
Конкретный медиатор устанавливает, кому передается и сообщение.
- Преимущества:
- Упрощаются объекты (выносим из них связи)
- Нет прямой зависимости между ними
- Происходит централизованное взаимодействие (появляется контроль)
- Недостатки:
- Использование посредников замедляет выполнение программы