Паттерны поведения: стратегия, шаблонный метод, посетитель, посредник, хранитель, команда. - Painted-Black/BMSTU-OOP GitHub Wiki
Паттерны поведения связаны с алгоритмами и распределением обязанностей между объектами.
Инкапсулирует запрос как объект, позволяя тем самым задавать параметры клиентов для обработки соответствующих запросов, ставить запросы в очередь или протоколировать их, а также поддерживать отмену операций.
Используйте паттерн команда, когда хотите:
- параметризовать объекты выполняемым действием. В процедурном языке такую параметризацию можно выразить с помощью функции обратного вызова
- определять, ставить в очередь и выполнять запросы в разное время
- поддержать отмену операций
- поддержать протоколирование изменений, чтобы их можно было выполнить повторно после аварийной остановки системы
- структурировать систему на основе высокоуровневых операций, построенных из примитивных
- Command
- объявляет интерфейс для выполнения операции
- ConcreteCommand
- определяет связь между объектом-получателем Receiver и действием
- реализует операцию Execute путем вызова соответствующих операций объекта Receiver
- Client
- создает объект класса ConcreteCommand и устанавливает его получателя
- Invoker
- обращается к команде для выполнения запроса
- Receiver
- располагает информацией о способах выполнения операций, необходимых для удовлетворения запроса. В роли получателя может выступать любой класс.
Результаты применения паттерна команда:
-
команда разрывает связь между объектом, иниуиирующим операцию, и объектом, имеющим информацию о том, как ее выполнять
-
команды - это объекты. Допускается манипулировать ими и расширять их точно так же, как в случае с любыми другими объектами
-
из простых команд можно собирать составные. В общем случае составные команды описываются паттерном компоновщик
-
добавлять новые команды легко, поскольку никакие существующие классы изменять не нужно.
class Command
{
public:
virtual void execute() = 0;
};template class SimpleCom: public Command { typedef void (Receiver::Action)(); public: SimpleCom(shared_ptr r, Action a): _r(r), _a(a) {} void execute() override { ((_r).*_a)(); { private: shared_ptr _r; Action _a; };
Позволяет избежать привязки отправителя запроса к его получателю, давая шанс обработать запрос нескольким объектам. Связывает объекты-получатели в цепочку и передает запрос вдоль этой цепочки, пока его не обработают.
Используйте цепочку обязанностей, когда:
- есть более одного объекта, способного обработать запрос, причем настоящий обработчик заранее неизвестен и должен быть найден автоматически
- вы хотите отправить хапрос одному из нескольких объектов, не указывая явно, какому именно
- набор объектов, способных обработать один запрос, должен задаваться динамически.
- Handler
- определяет интерфейс для обработки запросов
- (необязательно) реализует связь с преемником
- ConcreteHandler
- обрабатывает запрос,на который отвечает
- имеет доступ к своему прееснику
- если ConcreteHandler способен обработать запрос, то так и делает, если не может, то направляет его своему преемнику
- Client
- отправляет запрос некоторому объекту ConcreteHandler в цепочке.
Паттерн цепочка обязанностей имеет следующие достоинства и недостатки:
-
ослабление связанности
-
дополнителная гибкость при распределении обязаностей между объектами
-
получение не гарантированно.
class BaseHandler { public: void add(sharet_ptr elem); { if (next) next.->add(elem); else next = elem; virtual void handle() = 0; protected: sharet_ptr next; };
class Handler1: public BaseHandler { public: void handle() override { //... if (! условие) if (next) next->handle(); } };
Определяет зависимость типа "один ко многим" между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
Используйте паттерн наблюдатель в следующих случаях:
- когда у абстракции есть два аспекта, один из которых зависит от другого. Инкапсуляции этих аспектов в разные объекты позволяют изменять и повторно использовать их независиво;
- когда при модификации одного объекта требуется изменить другие и вы не знаете, сколько именно объектов нужно изменить;
- когда один объект должен оповещать других, не делая предположениц об уведомляемых объектах. Другими словамы, вы не хотите, чтобы объекты были тесно связаны между собой.
- Subject
- располагает информацией о своих наблюдателях. За субъектом можеь "следить" любое число наблюдателей
- предоставляет интерфейс для присоединения и отделения наблюдателей
- Observer
- определяет интерфейс обновления для объектов, которые должны быть уведомлены об изменении субъекта
- ConcreteSubject
- сохраняет состояние, представляющее интерес для конкретного наблюдателя ConcreteObserver
- посылает информацию своим наблюдателям, когда происходит изменение
- ConcreteObserver
- хранит ссылку на объект класса ConcreteSubject
- сохраняет данные, которые должны быть согласованы с данными субъекта
- реализует интерфейс обновления, определенный в классе Observer, чтобы поддержать согласованность с субъектом.
Объект ConcreteSubject уведомляет своих наблюдателей о любом изменении, которое могло бы привести к рассогласованности состояний наблюдателя и субъекта. После получения от конкретного субъекта уведомления ою изменении объект ConcreteObserver может запросить у субъекта дополнительную информацию, которую использует для того, чтобы оказаться в состоянии, согласованном с состоянием субъекта.
Достоинства и недостатки паттерна наблюдатель:
- абстрактная связанность субъекта и наблюдателя
- поддержка широковещательных коммуникаций
-
неожиданные обновления.
delegate void Eventhandler(Objet ^source, double d);
public ref class Manager // Издатель { // ref - размещение объекта в куче public: event EventHandler^ OnHandler; void Method() { OnHandler(this, 10.); } };
public ref class Watcher // Подписчик { public: Watcher(Manager ^m) { m->OnHandler += gcnew EventHandler(this, &Watcher::f) //& - потомучто метод класса // gcnew - управляемая куча } void f(doube d) {} };
// ... Manager ^m = gcnew Manager(); Watcher ^w = gcnew Watcher(m); m->Method();
Определяет объект, инкапсулирующий способ взаимодействия множества объектов. Посредник обеспечивает слабую связанность системы, избавляя объекты от необходимости явно ссылаться друг на друга и позволяя тем самым независимо изменять взаимодействия между ними.
Используйте паттерн посредник, когда:
- имеются объекты, связи между которыми сложны и четко определены. Получающиеся при этом взаимозависимости не структурированы и трудны для понимания;
- нельзя повторно использовать объект, поскольку он обменивается информацией со многими другими объектами;
- поведение, распределенное между несколькими классами, должно поддаваться настройке без порождения множества подклассов.
- Mediator
- определяет интерфейс для обмена информацией с объектами Colleague
- ConcreteMediator
- реализует кооперативное поведение, координируя действия объектов Colleague
- владеет информацией о коллегах и подсчитывает их
- Классы Colleague
- каждый класс Colleague "знает" о своем объекте Mediator
- все коллеги обмениваются информацией только с посредником, так как при его отсутствии им пришлось бы общаться напрямую.
Коллеги посылвают запросы посреднику и получают запросы от него. Посредник реализует кооперативное поведение путем переадресации каждого запроса подходящему коллеге (или нескольким).
Достоинства и недостатки паттерна посредник:
- снижает число порождаемых подклассов
- устраняет связанность между коллегами
- упрощает протоколы взаимодействия объектов
- абстрагирует способ кооперирования объектов
- централизует управление. Поскольку посредник инкапсулирует протоколы, то он может быть сложнее отдельных коллег. В результате сам посредник становится монолитом, который трудно сопровождать.
Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.
Используйте паттерн стратегия, когда:
- имеется много родственных классов, отличающихся только поведением. Стратегия позволяет сконфигурировать класс, задав одно из возможных поведений;
- вам нужно иметь несколько разных вариантов алгоритма. Стратегии разрешается применять, когда варианты алгоритмов реализованы в виде иерархии класов;
- в алгоритме содержатся данные, о которых клиент не должен "знать". Используйте паттерн стратегия, чтобы не раскрывать сложные, специфичные для алгоритма структуры данных;
- в классе определено много поведений, что предствалено разветвленными условными операторами. В этом случае проще перенести код из ветвей в отдельные классы стратегий.
- Strategy
- объявляет общий для всех поддерживаемых алгоритмов интерфейс. Класс Context пользуется этим интерфейсом для вызова конкретного алгоритма, определенного в классе ConcreteStrategy
- ConcreteStrategy
- реализует алгоритм, использующий интерфейс, объявленный в классе Strategy
- Context
- конфигурируется объектом класса ConcreteStrategy
- хранит ссылку на объект Strategy
- может определять интерфейс, который позволяет объекту Strategy получить доступ к данным контекста.
Классы Strategy и Context взаимодействуют для реализации выбранного алгоритма. Контекст может передать стратегии все необходимые алгоритму данные в момент его вызова. Вместо этого контекст может позволить обращаться к своим операциям в нужные моменты, передав ссылку на самого себя операциям класса Strategy.
Контекст переадресует запросы своих клиентов объекту-стратегии. Обычно клиент создает объект ConcreteStrategy и передает его контексту, после чего клиент "общается" исключительно с контекстом. Часто в распоряжении клиента находится несколько классов ConcreteStrategy, которые он может выбирать.
Достоинства и недостатки:
- семейства родственных алгоритмов
- альтернатива порождению подклассов
- с помощью стратегий можно избавиться от условных операторов
- выбор реализации
- клиенты должны "знать" о различных стратегиях
- обмен информацией между стратегией и контекстом
- увеличение числа объектов.
Шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые шаги алгоритма, не изменяя его структуру в целом.
Обычно является элеметом архитектурного домена.
Паттерн шаблонный метод следует использовать:
- чтобы однократно использовать инвариантные части алгоритма, оставляя реализацию изменяющегося поведения на усмотрение подклассов;
- когда нужно вычленить и локализовать в одном классе поведение, общее для всех подклассов, чтобы избежать дублирования кода
- для управления расширениями подклассов.
- AbstractClass
- определяет абстрактные примитивные операции, замещаемые в конкретных подклассах для реализации шагов алгоритма
- реализует шаблонный метод, определяющий скелет алгоритма. Шаблонный метод вызывает примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах.
- ConcreteClass
- реализует примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах.
ConcreteClass предполагает, что инвариантные шаги алгоритма будут выполнены в AbstractClass.
Шаблонные методы вызывают операции следующих видов:
- конкретные операции (либо из класса ConsreteClass, либо из классов клиента)
- конкретные операции из класса AbstractClass (то есть операции, полезные всем подклассам)
- примитивные операции (то есть абстрактные операции)
- фабричные методы
- операции-зацепки, реализующие поведение по умолчанию, которое может быть расширено в подклассах. Часто такая операция по умолчанию не делает ничего.
Описывает операцию, выполняемую с каждым объектом из некоторой структуры. Паттерн посетитель позволяет определить новую операцию, не изменяя классы этих объектов.
Используйте паттерн посетитель, когда:
- в структуре присутствуют объекты многих классов с различными интерфейсами и вы хотите выполнять над ними операции, зависящие от конкретных классов
- над объектами, входящими в состав структуры, надо выполнять разнообразные, не связаннын между собой операции и вы не хотите "засорять" классы такими операциями. Посетитель позволяет объединить родственные операции, поместив их в один класс. Если структура объектов является общей для нескольких приложений, то паттерн посетитель позволит в каждое приложение включить только относящиеся к нему операции;
- классы, устанавливающие структуру объектов, изменяются редко, но новые операции над этой структурой добавляются часто.
- Visitor
- объявляет операцию Visit для каждого класса ConcreteElement в структуре объектов. Имя и сигнатура этой операции идентифицируют класс, который посылает посетителю запрос Visit. Это позволяет посетителю определить, элемент какого конкретного класса он посещает. Владея такой информацией, посетитель может обращаться к элементу напрямую через его интерфейс
- ConcreteVisitor
- реализует все операции, объявленные в классе Visitor. Каждая операция реализует фрагмент алгоритма, определенного для класса соответствующего объекта в структуре. Класс ConcreteVisitor предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. Часто в этом состоянии аккумулируются результаты, полученные в процессе обхода структуры
- Element
- определяет операцию Accept, которая принимает посетителя в качестве аргумента
- ConcreteElement
- реализует операцию Accept, которая принимает посетителя в качестве аргумента
- ObjectStructure
- может перечислить свои элементы
- может предоставить посетителю высокоуровневый интерфейс для посещения своих элементов
- может быть как составным объектом, так и коллецией, например списком или множеством.
Клиент, использующий паттерн посетитель, должен создать объект класса ConcreteVisitor, а затем обойти всю структуру, посетив каждый ее элемент. При посещении элемента последний вызывает операцию посетителя, соответствующую своему классу. Элемент передает этой операции себя в качестве аргумента, чтобы посетитель мог при необходимости получить доступ к его состоянию.
Достоинства и недостатки:
- упрощает добавление новых операций
- объединяет родственные операции и отсекает те, которые не имеют к ним отношения
- посещение различных иерархий классов
- аккумулирование состояния
- добавление новых классов ConcreteElement затруднено
- нарушение инкапсуляции
Позволяет объекту варьировать свое поведение в зависмости от внутреннего состояния. Извне создается впечатление, что изменился класс объекта. Состояние объекта хранится в отдельном классе.
Используйте паттерн состояние в следующих случаях:
- когда поведение объекта зависит от его состояния и должно изменяться во время выполнения
- когда в кое операций встречаются состоящие из многих ветвей условные операторы, в которых выбор ветви зависит от состояния.
- Context
- определяет интерфейс, представляющий интерес для клиентов
- хранит экземпляр подкласса ConcreteState, которым определяется текущее состояние
- State
- определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным состоянием контекста Context
- Подклассы ConcreteState
- каждый подкласс реализует поведение, ассоциированное с некоторым сотоянием контекста Context. д Класс Context делегирует зависящие от состояния запросы текущему объекту ConcreteState. Контекст может передавать себя в качестве аргумента объекту State, который будет обрабатывать запрос. Это дает возможность объекту-состоянию при необходимости получить доступ к контексту. Context - это основной интерфейс для клиентов. Клиенты могут конфигурировать контекст объектами состояния State. Один раз сконфигурировав контекс, клиенты уже не должны напрямую связываться с объектами состояния. Либо Context, либо подклассы ConcreteState могут решить, при каких условиях и в каком порядке происходит смена состояний.
Результаты использования паттерна состояние:
- локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям
- делает явным переходы между состояниями
- объекты состояния можно разделять.
Предоставляет способ последовательного доступа ко всем элементам составного объекта, не раскрывая его внутреннего представляния.
Используйте паттерн итератор:
- для доступа к содержимому агрегированных объектов без раскрытия их внутреннего представления
- для поддержки нескольких активных обходов одного и того же агрегированного объекта
- для представления единообразного интерфейса с целью обхода различных агрегированных структур (то есть для поддержки полиморфной итерации).
- Iterator
- определяет интерфейс для доступа и обхода элементов
- ConcreteIterator
- реализует интерфейс класса Iterator
- следит за текущей позицией при обходе агрегата
- Aggregate
- определяет интерфейс для создания объекта-итератора
- ConcreteAggregate
- реализует интерфейс создания итератора и возвращает экземпляр подходящего класса ConcreteIterator.
ConcreteIterator отслеживает текущий объект в агрегате и может вычислить идущий за ним.
Особенности:
- поддерживает различные виды обхода агрегата
- итераторы упрощают интерфейс класса Aggregate
- одновременно для данного агрегата может быть активно несколько обходов.
Не нарушая инкапсуляции, фиксирует и выносит за пределы объекта его внутреннее состояние так, чтобы позднее можно было восстановить в нем объект.
Используйте паттерн хранитель, когда:
- необходимо сохранить мгновенный снимок состояния объекта (или его части), чтобы впоследствии объект можно было восстановить в том же состоянии
- прямое получение этого состояния раскрывает детали реализации и нарушает инкапсуляцию объекта.
- Memento
- сохраняет внутреннее состояние объекта Originator. Объем сохраняемой информации может быть различным и определяется потребностями хозяина
- запрещает доступ всем другим объектам, кроме хозяина
- Originator - хозяин
- создает хранитель, содержащий снимок текущего внутреннего состояния
- использует хранитель для восстановления внутреннего состояния
- Caretaker - посыльный
- отвечает за сохранение хранителя
- не производит никаких операция над хранителем и не исследует его внутреннее содержимое.
Посыльный запрашивает хранитель у хозяина, некоторое время держит его у себя, а затем возвращает хозяину. Иногда этого не происходит, так как последнему не нужно восстанавливать прежнее состояние. Хранители пассивны. Только хозяин, создавший хранитель, имеет доступ к информации о сотоянии.
Особенности паттерна хранитель:
- сохранение границ инкапсуляции
- упрощение структуры хозяина
- значительные издержки при использовании хранителей _ определение "узкого" и "широкого" интерфейсов. В некоторых языках сложно гарантировать, что только хозяин имеет доступ к состоянию хранителя
- скрытая плата за содержание хранителя.