Паттерны поведения: стратегия, шаблонный метод, посетитель, посредник, хранитель, команда. - Painted-Black/BMSTU-OOP GitHub Wiki

Паттерны поведения связаны с алгоритмами и распределением обязанностей между объектами.

Команда

Инкапсулирует запрос как объект, позволяя тем самым задавать параметры клиентов для обработки соответствующих запросов, ставить запросы в очередь или протоколировать их, а также поддерживать отмену операций.

Используйте паттерн команда, когда хотите:

  • параметризовать объекты выполняемым действием. В процедурном языке такую параметризацию можно выразить с помощью функции обратного вызова
  • определять, ставить в очередь и выполнять запросы в разное время
  • поддержать отмену операций
  • поддержать протоколирование изменений, чтобы их можно было выполнить повторно после аварийной остановки системы
  • структурировать систему на основе высокоуровневых операций, построенных из примитивных

Участники

Command

  • 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; };

Цепочка обязанностей

Позволяет избежать привязки отправителя запроса к его получателю, давая шанс обработать запрос нескольким объектам. Связывает объекты-получатели в цепочку и передает запрос вдоль этой цепочки, пока его не обработают.

Используйте цепочку обязанностей, когда:

  • есть более одного объекта, способного обработать запрос, причем настоящий обработчик заранее неизвестен и должен быть найден автоматически
  • вы хотите отправить хапрос одному из нескольких объектов, не указывая явно, какому именно
  • набор объектов, способных обработать один запрос, должен задаваться динамически.

Участники

ChainOfResponsibility

  • 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(); } };

Наблюдатель (Observer)

Определяет зависимость типа "один ко многим" между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.

Используйте паттерн наблюдатель в следующих случаях:

  • когда у абстракции есть два аспекта, один из которых зависит от другого. Инкапсуляции этих аспектов в разные объекты позволяют изменять и повторно использовать их независиво;
  • когда при модификации одного объекта требуется изменить другие и вы не знаете, сколько именно объектов нужно изменить;
  • когда один объект должен оповещать других, не делая предположениц об уведомляемых объектах. Другими словамы, вы не хотите, чтобы объекты были тесно связаны между собой.

Участники

Observer

  • 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)

Определяет объект, инкапсулирующий способ взаимодействия множества объектов. Посредник обеспечивает слабую связанность системы, избавляя объекты от необходимости явно ссылаться друг на друга и позволяя тем самым независимо изменять взаимодействия между ними.

Используйте паттерн посредник, когда:

  • имеются объекты, связи между которыми сложны и четко определены. Получающиеся при этом взаимозависимости не структурированы и трудны для понимания;
  • нельзя повторно использовать объект, поскольку он обменивается информацией со многими другими объектами;
  • поведение, распределенное между несколькими классами, должно поддаваться настройке без порождения множества подклассов.

Участники

Mediator

  • Mediator
    • определяет интерфейс для обмена информацией с объектами Colleague
  • ConcreteMediator
    • реализует кооперативное поведение, координируя действия объектов Colleague
    • владеет информацией о коллегах и подсчитывает их
  • Классы Colleague
    • каждый класс Colleague "знает" о своем объекте Mediator
    • все коллеги обмениваются информацией только с посредником, так как при его отсутствии им пришлось бы общаться напрямую.

Коллеги посылвают запросы посреднику и получают запросы от него. Посредник реализует кооперативное поведение путем переадресации каждого запроса подходящему коллеге (или нескольким).

Достоинства и недостатки паттерна посредник:

  • снижает число порождаемых подклассов
  • устраняет связанность между коллегами
  • упрощает протоколы взаимодействия объектов
  • абстрагирует способ кооперирования объектов
  • централизует управление. Поскольку посредник инкапсулирует протоколы, то он может быть сложнее отдельных коллег. В результате сам посредник становится монолитом, который трудно сопровождать.

Стратегия

Определяет семейство алгоритмов, инкапсулирует каждый из них и делает их взаимозаменяемыми. Стратегия позволяет изменять алгоритмы независимо от клиентов, которые ими пользуются.

Используйте паттерн стратегия, когда:

  • имеется много родственных классов, отличающихся только поведением. Стратегия позволяет сконфигурировать класс, задав одно из возможных поведений;
  • вам нужно иметь несколько разных вариантов алгоритма. Стратегии разрешается применять, когда варианты алгоритмов реализованы в виде иерархии класов;
  • в алгоритме содержатся данные, о которых клиент не должен "знать". Используйте паттерн стратегия, чтобы не раскрывать сложные, специфичные для алгоритма структуры данных;
  • в классе определено много поведений, что предствалено разветвленными условными операторами. В этом случае проще перенести код из ветвей в отдельные классы стратегий.

Участники

Strategy

  • Strategy
    • объявляет общий для всех поддерживаемых алгоритмов интерфейс. Класс Context пользуется этим интерфейсом для вызова конкретного алгоритма, определенного в классе ConcreteStrategy
  • ConcreteStrategy
    • реализует алгоритм, использующий интерфейс, объявленный в классе Strategy
  • Context
    • конфигурируется объектом класса ConcreteStrategy
    • хранит ссылку на объект Strategy
    • может определять интерфейс, который позволяет объекту Strategy получить доступ к данным контекста.

Классы Strategy и Context взаимодействуют для реализации выбранного алгоритма. Контекст может передать стратегии все необходимые алгоритму данные в момент его вызова. Вместо этого контекст может позволить обращаться к своим операциям в нужные моменты, передав ссылку на самого себя операциям класса Strategy.

Контекст переадресует запросы своих клиентов объекту-стратегии. Обычно клиент создает объект ConcreteStrategy и передает его контексту, после чего клиент "общается" исключительно с контекстом. Часто в распоряжении клиента находится несколько классов ConcreteStrategy, которые он может выбирать.

Достоинства и недостатки:

  • семейства родственных алгоритмов
  • альтернатива порождению подклассов
  • с помощью стратегий можно избавиться от условных операторов
  • выбор реализации
  • клиенты должны "знать" о различных стратегиях
  • обмен информацией между стратегией и контекстом
  • увеличение числа объектов.

Шаблонный метод

Шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые шаги алгоритма, не изменяя его структуру в целом.

Обычно является элеметом архитектурного домена.

Паттерн шаблонный метод следует использовать:

  • чтобы однократно использовать инвариантные части алгоритма, оставляя реализацию изменяющегося поведения на усмотрение подклассов;
  • когда нужно вычленить и локализовать в одном классе поведение, общее для всех подклассов, чтобы избежать дублирования кода
  • для управления расширениями подклассов.

Участники

TemplateMethod

  • AbstractClass
    • определяет абстрактные примитивные операции, замещаемые в конкретных подклассах для реализации шагов алгоритма
    • реализует шаблонный метод, определяющий скелет алгоритма. Шаблонный метод вызывает примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах.
  • ConcreteClass
    • реализует примитивные операции, а также операции, определенные в классе AbstractClass или в других объектах.

ConcreteClass предполагает, что инвариантные шаги алгоритма будут выполнены в AbstractClass.

Шаблонные методы вызывают операции следующих видов:

  • конкретные операции (либо из класса ConsreteClass, либо из классов клиента)
  • конкретные операции из класса AbstractClass (то есть операции, полезные всем подклассам)
  • примитивные операции (то есть абстрактные операции)
  • фабричные методы
  • операции-зацепки, реализующие поведение по умолчанию, которое может быть расширено в подклассах. Часто такая операция по умолчанию не делает ничего.

Посетитель (Visitor)

Описывает операцию, выполняемую с каждым объектом из некоторой структуры. Паттерн посетитель позволяет определить новую операцию, не изменяя классы этих объектов.

Используйте паттерн посетитель, когда:

  • в структуре присутствуют объекты многих классов с различными интерфейсами и вы хотите выполнять над ними операции, зависящие от конкретных классов
  • над объектами, входящими в состав структуры, надо выполнять разнообразные, не связаннын между собой операции и вы не хотите "засорять" классы такими операциями. Посетитель позволяет объединить родственные операции, поместив их в один класс. Если структура объектов является общей для нескольких приложений, то паттерн посетитель позволит в каждое приложение включить только относящиеся к нему операции;
  • классы, устанавливающие структуру объектов, изменяются редко, но новые операции над этой структурой добавляются часто.

Участники

Visitor

  • Visitor
    • объявляет операцию Visit для каждого класса ConcreteElement в структуре объектов. Имя и сигнатура этой операции идентифицируют класс, который посылает посетителю запрос Visit. Это позволяет посетителю определить, элемент какого конкретного класса он посещает. Владея такой информацией, посетитель может обращаться к элементу напрямую через его интерфейс
  • ConcreteVisitor
    • реализует все операции, объявленные в классе Visitor. Каждая операция реализует фрагмент алгоритма, определенного для класса соответствующего объекта в структуре. Класс ConcreteVisitor предоставляет контекст для этого алгоритма и сохраняет его локальное состояние. Часто в этом состоянии аккумулируются результаты, полученные в процессе обхода структуры
  • Element
    • определяет операцию Accept, которая принимает посетителя в качестве аргумента
  • ConcreteElement
    • реализует операцию Accept, которая принимает посетителя в качестве аргумента
  • ObjectStructure
    • может перечислить свои элементы
    • может предоставить посетителю высокоуровневый интерфейс для посещения своих элементов
    • может быть как составным объектом, так и коллецией, например списком или множеством.

Клиент, использующий паттерн посетитель, должен создать объект класса ConcreteVisitor, а затем обойти всю структуру, посетив каждый ее элемент. При посещении элемента последний вызывает операцию посетителя, соответствующую своему классу. Элемент передает этой операции себя в качестве аргумента, чтобы посетитель мог при необходимости получить доступ к его состоянию.

Достоинства и недостатки:

  • упрощает добавление новых операций
  • объединяет родственные операции и отсекает те, которые не имеют к ним отношения
  • посещение различных иерархий классов
  • аккумулирование состояния
  • добавление новых классов ConcreteElement затруднено
  • нарушение инкапсуляции

Состояние (State)

Позволяет объекту варьировать свое поведение в зависмости от внутреннего состояния. Извне создается впечатление, что изменился класс объекта. Состояние объекта хранится в отдельном классе.

Используйте паттерн состояние в следующих случаях:

  • когда поведение объекта зависит от его состояния и должно изменяться во время выполнения
  • когда в кое операций встречаются состоящие из многих ветвей условные операторы, в которых выбор ветви зависит от состояния.

Участники

State

  • Context
    • определяет интерфейс, представляющий интерес для клиентов
    • хранит экземпляр подкласса ConcreteState, которым определяется текущее состояние
  • State
    • определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным состоянием контекста Context
  • Подклассы ConcreteState
    • каждый подкласс реализует поведение, ассоциированное с некоторым сотоянием контекста Context. д Класс Context делегирует зависящие от состояния запросы текущему объекту ConcreteState. Контекст может передавать себя в качестве аргумента объекту State, который будет обрабатывать запрос. Это дает возможность объекту-состоянию при необходимости получить доступ к контексту. Context - это основной интерфейс для клиентов. Клиенты могут конфигурировать контекст объектами состояния State. Один раз сконфигурировав контекс, клиенты уже не должны напрямую связываться с объектами состояния. Либо Context, либо подклассы ConcreteState могут решить, при каких условиях и в каком порядке происходит смена состояний.

Результаты использования паттерна состояние:

  • локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям
  • делает явным переходы между состояниями
  • объекты состояния можно разделять.

Итератор

Предоставляет способ последовательного доступа ко всем элементам составного объекта, не раскрывая его внутреннего представляния.

Используйте паттерн итератор:

  • для доступа к содержимому агрегированных объектов без раскрытия их внутреннего представления
  • для поддержки нескольких активных обходов одного и того же агрегированного объекта
  • для представления единообразного интерфейса с целью обхода различных агрегированных структур (то есть для поддержки полиморфной итерации).

Участники

Iterator

  • Iterator
    • определяет интерфейс для доступа и обхода элементов
  • ConcreteIterator
    • реализует интерфейс класса Iterator
    • следит за текущей позицией при обходе агрегата
  • Aggregate
    • определяет интерфейс для создания объекта-итератора
  • ConcreteAggregate
    • реализует интерфейс создания итератора и возвращает экземпляр подходящего класса ConcreteIterator.

ConcreteIterator отслеживает текущий объект в агрегате и может вычислить идущий за ним.

Особенности:

  • поддерживает различные виды обхода агрегата
  • итераторы упрощают интерфейс класса Aggregate
  • одновременно для данного агрегата может быть активно несколько обходов.

Хранитель (Memento)

Не нарушая инкапсуляции, фиксирует и выносит за пределы объекта его внутреннее состояние так, чтобы позднее можно было восстановить в нем объект.

Используйте паттерн хранитель, когда:

  • необходимо сохранить мгновенный снимок состояния объекта (или его части), чтобы впоследствии объект можно было восстановить в том же состоянии
  • прямое получение этого состояния раскрывает детали реализации и нарушает инкапсуляцию объекта.

Участники

Memento

  • Memento
    • сохраняет внутреннее состояние объекта Originator. Объем сохраняемой информации может быть различным и определяется потребностями хозяина
    • запрещает доступ всем другим объектам, кроме хозяина
  • Originator - хозяин
    • создает хранитель, содержащий снимок текущего внутреннего состояния
    • использует хранитель для восстановления внутреннего состояния
  • Caretaker - посыльный
    • отвечает за сохранение хранителя
    • не производит никаких операция над хранителем и не исследует его внутреннее содержимое.

Посыльный запрашивает хранитель у хозяина, некоторое время держит его у себя, а затем возвращает хозяину. Иногда этого не происходит, так как последнему не нужно восстанавливать прежнее состояние. Хранители пассивны. Только хозяин, создавший хранитель, имеет доступ к информации о сотоянии.

Особенности паттерна хранитель:

  • сохранение границ инкапсуляции
  • упрощение структуры хозяина
  • значительные издержки при использовании хранителей _ определение "узкого" и "широкого" интерфейсов. В некоторых языках сложно гарантировать, что только хозяин имеет доступ к состоянию хранителя
  • скрытая плата за содержание хранителя.
⚠️ **GitHub.com Fallback** ⚠️