2.4 Patterns of behavior

Lecture



Behavioral patterns are associated with algorithms and the distribution of responsibilities between objects. It is not only about the objects and classes themselves, but also about the typical ways of interaction. Behavioral patterns characterize a complex flow of control that is difficult to trace during program execution. Attention is focused not on the control flow as such, but on the connections between the objects.

2.4.1 Iterator Pattern

Title

Iterator (iterator). Cursor (cursor)

Task

Provides a way to sequential access to all elements of a compound object, without revealing its internal representation.

Motivation

A composite object, say a list, should provide a way to access its elements without revealing their internal structure. Moreover, it is sometimes necessary to bypass the list in different ways, depending on the problem being solved. But you hardly want to clutter the List class interface with operations for various workarounds, even if all of them can be foreseen in advance. In addition, it is sometimes necessary that several active list traversals be defined at the same time. All this allows to make a pattern iterator. Its main idea is that it’s not the list itself that is responsible for access to the elements and the workaround, but a separate object — an iterator. The class Iterator defines an interface for accessing the elements of the list. An object of this class keeps track of the current element, that is, it has information about which elements have already been visited. For example, the List class could provide the ListIterator class.

2.4 Patterns of behavior

Figure 2.21 Iterator

Before you create an instance of the Listlterator class, you must have a list to be crawled. With the Listlterator object, you can sequentially visit all the elements of the list. The surrentltem operation returns the current item in the list, the first operation initializes the current item to the first item in the list, Next makes the next item current, and eof checks if we are behind the last item, if so, the crawl is completed.

Separating the traversal mechanism from the List object allows you to define iterators that implement various traversal strategies without listing them in the List class interface. For example, FilteringListIterator could provide access only to those elements that satisfy the filtering conditions. Note: there is a close relationship between the iterator and the list, the client should have information that he is walking around the list, and not some other aggregated structure. Therefore, the client is tied to a specific aggregation method. It would be better if we could change the class of the unit, without touching the client code. This can be done by generalizing the concept of the iterator and examining the polymorphic iteration. For example, suppose that we still have the SkipList class that implements the list. A skiplist is a probabilistic data structure that resembles a balanced tree by characteristics. We need to learn how to write code that can work with objects of both the List class and the SkipList class.

2.4 Patterns of behavior

Figure 2.22 Iterator pattern structure (example)

Separating the traversal mechanism from the List object allows you to define iterators that implement various traversal strategies without listing them in the List class interface. For example, FilteringListIterator could provide access only to those elements that satisfy the filtering conditions. Note: there is a close relationship between the iterator and the list, the client should have information that he is walking around the list, and not some other aggregated structure. Therefore, the client is tied to a specific aggregation method. It would be better if we could change the class of the unit, without touching the client code. This can be done by generalizing the concept of the iterator and examining the polymorphic iteration. For example, suppose that we still have the SkipList class that implements the list. A skiplist is a probabilistic data structure that resembles a balanced tree by characteristics. We need to learn how to write code that can work with objects of both the List class and the SkipList class.

We define the AbstractList class, in which the common interface for list manipulation is declared. We also need an abstract class Iterator, which defines the general iteration interface. Then we would be able to define specific subclasses of the Iterator class for various implementations of the list. As a result, the iteration mechanism is not dependent on specific aggregated classes.

Structure

The structure of the pattern is shown in fig. 2.23.

  • Iterator is an iterator; defines an interface for accessing and traversing items;
  • Concretelterator - a specific iterator; - implements Iterator class interface; - monitors the current position when traversing the unit;
  • Aggregate - aggregate: - defines the interface for creating an iterator object;
  • ConcreteAggregate is a specific aggregate: - implements an interface for creating an iterator and returns an instance of a suitable class Concretelterator.

results

The iterator pattern has the following important features:

  • supports various types of bypass of the unit. Complex units can be bypassed in different ways. For example, for code generation and semantic checks, it is necessary to bypass parse trees. The code generator can bypass the tree in an internal or direct order. Iterators make it easy to change the traversal algorithm — simply replacing one instance of the iterator with another. To support new types of traversal, you can define Iterator class subclasses;
  • iterators simplify the Aggregate class interface. The presence of an interface for traversal in the Iterator class makes redundant duplication of this interface in the Aggregate class. Thus, the interface of the unit is simplified;
  • several rounds may be active for this unit at the same time. The iterator keeps track of the bypass state itself encapsulated in it. Therefore, several rounds of the unit are allowed at the same time.

2.4 Patterns of behavior

Figure 2.23 Iterator Pattern Structure

2.4.2 Mediator Pattern

Title

Mediator (Mediator)

Task

Defines an object that encapsulates the way that multiple objects interact. The mediator provides a weak connectivity of the system, eliminating the objects from having to explicitly refer to each other and thereby allowing independently changing the interactions between them.

Motivation

Object-oriented design contributes to the distribution of some behavior between objects. But at the same time in the resulting structure of objects there can be many connections or (in the worst case) each object will have to have information about all the others. Although splitting the system into multiple objects generally increases the degree of reuse, however, an abundance of interrelationships leads to the opposite effect. If there are too many interconnections, then the system is like a monolith and it is unlikely that the object will be able to work without the support of Other objects. Moreover, it is practically impossible to significantly change the behavior of the system, since it is distributed among many objects. If you make a similar attempt, you will have to define many subclasses to tune the system behavior.

Consider the implementation of dialog boxes in a graphical user interface. Here is a row of widgets: buttons, input fields, etc., as shown in Figure. 2.24.

2.4 Patterns of behavior

Figure 2.24. Search form

Often there are dependencies between different widgets in the dialog box. For example, if one of the input fields is empty, then a certain button is not available. If you select from the list, the contents of the input field may change. Conversely, entering text into a field may automatically lead to the selection of one or more list items. If some text is present in the input field, then buttons can be activated that allow you to perform a specific action on this text, for example, change or delete it.

2.4 Patterns of behavior

Figure 2.25 Hierarchy class of window widgets using an intermediary

2.4 Patterns of behavior

Figure 2.26 Typical event processing sequence

In different dialog boxes, dependencies between widgets can be different. Therefore, in spite of the fact that widgets of the same type are found in all windows, it will not be possible to simply pick up and reuse ready-made widget classes, you will have to configure to take account of dependencies. Individual setting of each widget is a tedious task, because there are a lot of participating classes.

All these problems can be avoided by encapsulating collective behavior in a separate mediator object. The mediator is responsible for coordinating the interactions between the group of objects. It eliminates the objects in the group from having to explicitly refer to each other. All objects have information only about the intermediary, so the number of relationships is reduced. Thus, the UIFind class can serve as an intermediary between widgets in the dialog box. An object of this class "knows" about all the widgets in the window.

The structure of classes obtained as a result of the use of a mediator for the example shown in Fig. 2.24, can be seen in fig. 2.25, and the interaction diagram is shown in fig. 2.26.

Structure

2.4 Patterns of behavior

Figure 2.27 Structure of the Mediator pattern.

  • Mediator mediator: - defines an interface for exchanging information with objects C;
  • ConcreteMediator is a specific mediator: - implements cooperative behavior, coordinating the actions of C objects;
  • Classes C, A, B. Each of these classes "knows" about its Mediator object; - all objects of these classes exchange information only with an intermediary, since in its absence they would have to communicate with each other directly

results

The mediator pattern has the following advantages and disadvantages:

  • eliminates cohesion between subclasses C. Classes and Mediator can be changed independently of each other;
  • simplifies the protocols of interaction of objects. The mediator replaces the “all with all” interaction discipline with the “one with all” discipline, that is, one mediator interacts with all subclasses of C. Relationships of the “one-to-many” type are easier to understand, maintain and expand;
  • abstracts a way of cooperating objects. Allocation of the mediation mechanism into a separate concept and encapsulation of it in one object allows you to concentrate on the interaction of objects, and not on their individual behavior. This makes it possible to clarify the interactions existing in the system;
  • centralizes management. The mediator pattern transfers the interaction complexity to the mediator class. Since the mediator encapsulates the protocols, it can be more complicated than individual C. As a result, the mediator himself becomes a monolith that is difficult to accompany.

2.4.3 Observer Pattern

Title

Observer. Dependents, Publish-Subscribe (Publisher Subscriber).

Task

Defines a one-to-many type dependency between objects in such a way that when a state of a single object changes, all dependent objects are notified of this and automatically updated.

Motivation

As a result of splitting the system into many jointly working classes, it becomes necessary to maintain a consistent state of interrelated objects. But I wouldn’t like it to be necessary to pay for coherence with rigidly connected classes, since this somewhat reduces the possibility of reuse. For example, in many libraries for building graphical user interfaces, the presentational aspects of the interface are separated from the application data. You can work autonomously with classes that describe data and their presentation. The spreadsheet and chart do not have information about each other, so you can use them separately. But they behave as if they "know" about each other. When a user works with a table, all changes are immediately reflected in the diagram, and vice versa.

With this behavior, it is implied that both the spreadsheet and the chart depend on the object data and therefore must be notified of any changes in its state. And there are no reasons limiting the number of dependent objects; to work with the same data there can be any number of user interfaces.

The observer pattern describes how to establish such relationships. The key objects in it are the subject and the observer. A subject can have as many observers as he wishes. All observers are notified of changes in the state of the subject. After receiving the notification, the observer polls the subject in order to synchronize his state with him. This kind of interaction is often called a publisher-subscriber relationship. The subject issues or publishes notices and sends them out without even knowing which objects are subscribers. An unlimited number of observers can subscribe to receive notifications.

Structure and participants

  • Subject - subject: has information about their observers. A subject can be “watched” by any number of observers; the subject also provides an interface for joining and separating observers;
  • Observer Observer: defines the update interface for objects that should be notified of a change in subject;
  • ConcreteSubject - specific subject: saves a state of interest to a particular observer ConcreteObserver; sends information to its observers when a change occurs;
  • ConcreteObserver - a specific observer; Stores a link to a specific subject to access the status of the latter.

2.4 Patterns of behavior

Figure 2.28 Observer Pattern Structure

results

The observer pattern allows you to change subjects and observers independently of each other. Subjects are allowed to reuse without the participation of observers, and vice versa. This makes it possible to add new observers without modifying the subject or other observers.

Consider some of the advantages and disadvantages of the observer pattern:

  • Abstract connectedness of subject and observer. The subject has information only that he has a number of observers, each of whom obeys a simple interface of the abstract class Observer. Subject unknown specific classes of observers. Thus, communication between subjects and observers are abstract and minimized. Since the subject and the observer are not closely related, they can be at different levels of the abstraction of the system. A lower level entity may notify observers at the upper levels without breaking the hierarchy of the system. If the subject and the observer were a single whole, then the resulting object would either cross the level boundaries (violating the principle of their formation), or would have to be at some one level (compromising the level abstraction);
  • Support for broadcast communications. Unlike a regular request, a notification sent by a subject does not need to specify a specific recipient. The notification is automatically received by all objects subscribing to it. The subject does not need information on the number of such objects, he is only required to notify his observers. Therefore, we can at any time add and remove observers. The observer decides whether to process the received notification or ignore it;
  • Unexpected updates. Since the observers do not have information about each other, they do not know about the cost of changing the subject. A seemingly harmless operation on a subject can cause a whole series of updates by observers and objects dependent on them. Moreover, poorly defined or poorly supported dependency criteria can cause unforeseen updates, which are very difficult to track. This problem is aggravated by the fact that the simple update protocol does not contain any information about what exactly has changed in the subject. Without an additional protocol to help figure out the nature of the changes, observers will have to do a difficult job to indirectly obtain such information.

2.4.4 State Pattern

Title

State

Task

Allows an object to vary its behavior depending on its internal state. From the outside it seems that the class of the object has changed.

Motivation

Consider the TCPConnection class that represents a network connection. An object of this class can be in one of several states: Established, Listening, Closed. When a TCPConnection object receives requests from other objects, It responds differently depending on the current state. For example, the response to an open request depends on whether the connection is in the Closed state or Established state. The state pattern describes how a TCPConnection object can behave differently when in different states.

The basic idea behind this pattern is to introduce the abstract TCPState class to represent various connection states. This class declares an interface common to all classes describing various operating states. TCPState subclasses implement state-specific behavior. For example, in the TCPEstablished and TCPClosed classes the behavior characteristic of the Established and Closed states is implemented.

Класс TCPConnection хранит у себя объект состояния (экземпляр некоторого подкласса TCPState), представляющий текущее состояние соединения, и делегирует все зависящие от состояния запросы этому объекту. TCPConnection использует свой экземпляр подкласса TCPState для выполнения операций, свойственных только данному состоянию соединения.

2.4 Patterns of behavior

Рисунок 2.29 Структура паттерна State (пример)

При каждом изменении состояния соединения TCPConnection изменяет свой объект-состояние. Например, когда установленное соединение закрывается, TCPConnection заменяет экземпляр классаTCPEstablished экземпляром TCPClosed.

Паттерн State имеет смысл использовать в следующих ситуациях:

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

Structure

2.4 Patterns of behavior

Рисунок 2.30 Структура паттерна State

results

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

Делает явными переходы между состояниями. Если объект определяет свое текущее состояние исключительно в терминах внутренних данных, то переходы между состояниями не имеют явного представления; они проявляются лишь как присваивания некоторым переменным. Ввод отдельных объектов для различных состояний делает переходы более явными. Кроме того, объекты State могут защитить контекст Context от рассогласования внутренних переменных, поскольку переходы с точки зрения контекста - это атомарные действия. Для осуществления перехода надо изменить значение только одной переменной (объектной переменной State в классе Context), а не нескольких;

2.4.5 Паттерн Strategy

Title

Strategy (стратегия). Policy (политика) .

Task

Defines a family of algorithms, encapsulates each of them, and makes them interchangeable. The strategy allows you to change the algorithms, regardless of the customers who use them.

Motivation

There are many algorithms for splitting text into lines. It is hard to “sew up” the weight of such algorithms into classes that need them for several reasons:

  • A client who needs a breakdown algorithm is complicated when the corresponding code is included in it. Thus, clients become more cumbersome, and it is more difficult to accompany them, especially if you need to support several algorithms at once;
  • Depending on the circumstances, it is worth applying one or another algorithm. I would not like to support several line breaks if we don’t use them;
  • Если разбиение на строки - неотъемлемая часть клиента, то задача добавления новых и модификации существующих алгоритмов усложняется.

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

2.4 Patterns of behavior

Рисунок 2.31 Структура паттерна Strategy (пример)

Предположим, что класс Composition отвечает за разбиение на строки текста, отображаемого в окне программы просмотра, и его своевременное обновление. Стратегии разбиения на строки определяются не в классе Composition, а в подклассах абстрактного класса Compositor. Это могут быть, например, такие стратегии:

  • SimpleCompositor реализует простую стратегию, выделяющую по одной строке за раз;
  • TeXCompositor реализует алгоритм поиска точек разбиения на строки, принятый в редакторе TeX. Эта стратегия пытается выполнить глобальную оптимизацию разбиения на строки, рассматривая сразу целый параграф;
  • Array-Compositor реализует стратегию расстановки переходов на новую строку таким образом, что в каждой строке оказывается одно и то же число элементов. Это полезно, например, при построчном отображении набора пиктограмм.

Объект Composition хранит ссылку на объект Compositor. Всякий раз, когда объекту Composition требуется переформатировать текст, он делегирует данную обязанность своему объекту Compositor. Клиент указывает, какой объект Compositor следует использовать, параметризуя им объект Composition.

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

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

Structure and participants

  • Strategy (Compositor) - стратегия: объявляет общий для всех поддерживаемых алгоритмов интерфейс. Класс Context пользуется этим интерфейсом для вызова конкретного алгоритма, определенного в классеConcreteStratecjy;
  • ConcreteStrategy (SirnpleCompositor, TeXCompositor, ArrayCompositor) - конкретная стратегия: - реализует алгоритм, использующий интерфейс, объявленный в классе Strategy;
  • Context (Composition) - контекст: - конфигурируется объектом класса ConcreteStrategy; хранит ссылку на объект класса Strategy; может определять интерфейс, который позволяет объекту strategy получить доступ к данным контекста.

2.4 Patterns of behavior

Рисунок 2.32 Структура паттерна Strategy

results

У паттерна стратегия есть следующие достоинства и недостатки:

  • С помощью стратегий можно избавиться от условных операторов. Благодаря паттерну стратегия удается отказаться от условных операторов при выборе нужного поведения. Когда различные поведения помещаются в один класс, трудно выбрать нужное без применения условных операторов. Инкапсуляция же каждого поведения в отдельный класс Strategy решает эту проблему. Так, без использования стратегий код для разбиения текста на строки мог бы выглядеть следующим образом:

void Composition::Repair ()

{

switch (_breakingStrategy) {

case SimpleStrategy:

ComposeWithSimpleCompositor();

break;

case TeXStrategy:

ComposeWithTeXCorapositor();

break;

// ...

}

// если необходимо, объединить результаты

// с имеющейся композицией

}

Паттерн стратегия позволяет обойтись без оператора переключения за счет делегирования задачи разбиения на строки объекту Strategy:

void Composition::Repair ()

{

_compositor->Compose();

// если необходимо, объединить результаты

// с имеющейся композицией

}

Если код содержит много условных операторов, то часто это признак того, что нужно применить паттерн стратегия;

  • Выбор реализации. Стратегии могут предлагать различные реализации одного и того ж- поведения. Клиент вправе выбирать подходящую стратегию в зависимости от своих требований к быстродействию и памяти;
  • Клиенты должны «знать» о различных стратегиях. Потенциальный недостаток этого паттерна в том, что для выбора подходящей стратегии клиент должен понимать, чем отличаются разные стратегии. Поэтому наверняка придется раскрыть клиенту некоторые особенности реализации. Отсюда следует, что паттерн стратегия стоит применять лишь тогда, когда различия в поведении имеют значение для клиента;
  • Обмен информацией между стратегией и контекстом. Интерфейс класса Strategy разделяется всеми подклассами ConcreteStrategy - неважно, сложна или тривиальна их реализация. Поэтому вполне вероятно, что некоторые стратегии не будут пользоваться всей передаваемой им информацией, особенно простые. Это означает, что в отдельных случаях контекст создаст и проинициализирует параметры, которые никому не нужны. Если возникнет проблема, то между классами Strategy и Context придется установить более тесную связь;

2.4.6 Паттерн Template Method

Title

Template Method (шаблонный метод).

Task

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

Motivation

Рассмотрим каркас приложения, в котором имеются классы Application Document. Класс Application отвечает за открытие существующих документов, хранящихся во внешнем формате, например в виде файла. Объект класса Document представляет информацию документа после его прочтения из файла.

Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов Application и Document, отвечающие конкретным потребностям. Например, графический редактор определит подклассы DrawApplication и DrawDocument, а электронная таблица - подклассы SpreadsheetApplication и SpreadsheetDocument.

2.4 Patterns of behavior

Рисунок 2.33 Структура паттерна Template Method (пример)

В абстрактном классе Application определен алгоритм открытия и считывания документа в операции OpenDocument;

void Application::openDocument (const char* name)

{

if (! CanOpenDocument( name)) {

// работа с этим документом

// невозможна return;

}

Document* doc = DoCreateDocument();

if (doc) {

_docs->AddDocument(doc);

AboutToOpen Document (doc);

doc->0pen();

doc-> DoRead();

}

}

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

Операцию вида openDocument мы будем называть шаблонным методом, описывающим алгоритм в терминах абстрактных операций, которые замещены в подклассах для получения нужного поведения. Подклассы класса Application выполняют проверку возможности открытия (canOpenDocument) и создания документ (doCreateDocument). Подклассы класса Document считывают документ (doRead). Шаблонный метод определяет также операцию, которая позволяет подклассам Application получить информацию о том, что документ вот-вот будет открыт (aboutToOpenDocurnent). Определяя некоторые шаги алгоритма с помощью абстрактных операций, шаблонный метод фиксирует их последовательность, но и позволяет реализовать их в подклассах классов Application и Document.

Structure

2.4 Patterns of behavior

Рисунок 2.34 Структура паттерна Template Method

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

results

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

Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой кино-империи фразу «Не звоните нам, мы сами позвоним». В данном случае это означает, что родительский класс вызывает операции подкласса, а не наоборот.

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

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

It is important that the template method clearly distinguishes the hook operations (which can be replaced) and abstract operations (which need to be replaced). To reuse an abstract class with maximum efficiency, subclass authors need to understand which operations are intended for substitution.

A subclass can extend the behavior of some operation by replacing it and explicitly calling this operation from the parent class:

void DerivedClass :: 0peration ()

{

ParentClass :: Operation));

// Extended DerivedClass Behavior

}

Unfortunately, it is very easy to forget about the need to invoke an inherited operation. We have the ability to transform such an operation into a template method with the goal of giving the parent control over how the subclasses extend it. The idea is to invoke the hook operation from the template method in the parent class. Then subclasses can override this particular operation:

void ParentClass :: Operation ()

{

// Behavior of the parent class ParentClass

HookOperation ();

}

In the ParentClass parent class, the Hook0peration operation does nothing:

void ParentClass :: HookOperation ()

{

}

But it is replaced in subclasses that extend the behavior:

void DerivedClass :: HookOperation ()

{

// extension in derived class

}

2.4.7 Pattern Chain Of Responsibility

Title

Chain Of Responsibility ( chain duties )

Purpose

Allows you to avoid binding the sender of the request to his recipient, giving a chance to process the request to several objects. Binds the recipient objects into a chain and sends the request along this chain until it is processed.

Motivation

Consider the context-sensitive online help in the graphical user interface, which can obtain additional information on any part of the interface by simply clicking on it with the mouse. The content of the help depends on what part of the interface and in what context is selected. For example, help on a button in a dialog box may differ from help on a similar button in the main application window. If there is no help for some part of the interface, the system should show information about the immediate context in which it is located, for example, about the dialog box as a whole.

Therefore, it would be natural to organize reference information from more specific sections to more general ones. In addition, it is clear that the request for help is processed by one of several user interface objects, which one depends on the context and the information available. The problem is that the object initiating the request (for example, a button) does not have information about which object will ultimately provide help. We need some way to separate the request initiator button from the objects that own the reference information. How to achieve this shows the chain of duty pattern. The idea is to break the link between senders and recipients, giving the opportunity to process the request for several objects. The request moves through the chain of objects until one of them processes it. The first object in the chain receives the request and either processes it itself, or sends it to the next candidate in the chain, who behaves in the same way. The object that sent the request has no information about the handler. We say that the request has an anonymous receiver (implicit receiver).

Suppose a user requests help for the Print button. It is located in the PrintDialog dialog box containing information about the application object to which it belongs (see the previous object diagram). Presented in Fig. 2.34 interaction diagram shows how the request for help moves through the chain.

2.4 Patterns of behavior

Figure 2.35 Chain of Responsibility pattern. Relationships between objects

2.4 Patterns of behavior

Figure 2.36 Chain of Responsibility pattern. Message processing

In this case, neither the aPrintButton button nor the aPrintDialog window processes the request; it reaches an anApplication object, which can process or ignore it. The client initiating the request does not have a direct link to the object that will eventually execute it.

To poison a request by chain and ensure recipient anonymity, all objects in the chain have a single interface for processing requests and for accessing their successor (the next object in the chain). For example, in the online help system, one would define a HelpHandler class (the ancestor of the classes of all candidate objects or a mix-in class (mixin class)) with the HandleHelp operation. Then the classes that will process the request will be able to pass it on to their parent.

To handle requests for help, the Button, PrintDialog, and Application classes use HelpHandler operations. By default, HandleHelp simply forwards the request to its successor. In subclasses, this operation is replaced, so that under favorable circumstances reference information may be given. Otherwise, the request is sent further through the default implementation.

2.4 Patterns of behavior

Figure 2.37 The structure of the Chain of Responsibility pattern (example)

Applicability

  • There is more than one object capable of processing a request, and the real handler is unknown in advance and should be found automatically.
  • You want to send a request to one of several objects, without explicitly indicating which one;
    the set of objects capable of processing the request must be specified dynamically.

Structure

2.4 Patterns of behavior

Figure 2.38 The structure of the Chain of Responsibility pattern

results

  • The weakening of connectedness. This pattern frees the object from having to “know” who will specifically process its request. The sender and receiver do not know anything about each other, and the object included in the chain about the structure of the chain. Thus, the chain of responsibilities helps to simplify the relationship between objects. Instead of storing references to all objects that can become recipients of the request, the object should have information only about its nearest successor;
  • Additional flexibility in the allocation of responsibilities between objects. The chain of responsibilities allows you to increase the flexibility of the distribution of duties between objects. You can add or change responsibilities for processing a request by including new members in the chain or by changing it in some other way. This approach can be combined with static generation of subclasses to create specialized handlers;
  • Receipt is not guaranteed. Since the request has no explicit recipient, there is no guarantee that it will be processed at all: it can reach the end of the chain and disappear. The request may be unprocessed even in the case of incorrect configuration of the chain.

2.4.8 Command Pattern

Title

Command (command), Action (action), Transaction (transaction)

Purpose

Encapsulates the request as an object, thereby allowing you to set the parameters of the Clients to process the relevant requests, put requests in the queue or log them, and also support cancellation of operations.

Motivation

Sometimes it is necessary to send requests to objects without knowing anything about what operation is requested and who is the recipient. For example, in libraries for building user interfaces there are such objects as buttons and menus that send a request in response to a user action. But the library itself does not have the ability to process this request, since only the application using it has information about what should be done. The designer of the library does not have any information about the recipient of the request and what operations he must perform. The command pattern allows library objects to send requests to unknown objects of the application, converting the request itself into an object. This object can be stored and transmitted, like any other. The basis of the pattern being written off is the abstract Command class, in which the interface for performing operations is declared. In its simplest form, this interface consists of a single Execute abstract operation. Specific Command subclasses define a “recipient-action” pair, storing the recipient in an instance variable, and implement the Execute operation so that it sends the request. The recipient has the information necessary to fulfill the request.

2.4 Patterns of behavior

Figure 2.39 Command Pattern Structure (example)

With the help of Command objects easily implemented menu. Each menu item is an instance of the Menultem class. The menus themselves and all of their items create a class Application along with all the other elements of the user interface. The Application class also keeps track of user-open documents.

The application configures each Menultem object with an instance of a specific Command subclass. When the user selects a menu item, the associated Menultem object calls Execute for its command object, and Execute performs the operation. Menultem objects do not have information on which subclass of Command class they use. Command subclasses store information about the recipient of the request and invoke one or more operations of this recipient.

For example, a subclass of PasteCommand supports pasting text from the clipboard into a document. The recipient for PasCeCommand is the Document that was transmitted when the object was created. The Execute operation invokes the Paste operation of the receiving document.

For the OpenCommand subclass, the Execute operation behaves differently: it queries the user for the document name, creates the corresponding Document object, notifies the receiving application of the new document, and opens this document.

Sometimes a Menultem object must perform a sequence of commands — for example, a menu item for centering a standard-sized page could be constructed from two objects at once: CenterDocumentCommand and NormalsizeCommand. Since such a combination of commands is a common phenomenon, we can define the MacroCommand class, which allows the Menultem object to execute an arbitrary number of commands. MacroCommand is a concrete subclass of the Command class that simply executes a sequence of commands. He does not have an explicit recipient, since each team has its own specific one.

Note that in each of the examples given, the command command separates the object initiating the operation from the object that “knows” how to execute it. This allows for high flexibility in designing the user interface. A menu item and a button can be simultaneously associated in an application with a certain function; to do this, it is sufficient to assign the same instance of a particular Command class subclass to both elements. We can dynamically replace commands, which is very useful for implementing context-sensitive menus. You can also support scripts by assembling simple commands into more complex ones. All this is doable because the object initiating the request must have information only about that. how to send it, not how to execute it.

2.4 Patterns of behavior

Figure 2.40. Command Pattern Macro.

Applicability

  • you want to parameterize objects with the action to be performed, as is the case with the menu items Menultem. In procedural language, such parameterization can be expressed using a callback function, that is, a function that is registered to be called later. Commands are an object-oriented alternative to callback functions;
  • need to determine, queue and execute requests at different times. The lifetime of the Command object does not need to depend on the lifetime of the original request. If the recipient of the request can be implemented so that it does not depend on the address space, then the command object can be transferred to another process, which will be engaged in its execution;
  • want to support undo operations. The Execute operation of the Command object can save the state required to roll back actions performed by a command. In this case, the Command class interface should have an additional Unexecute operation that cancels the actions performed by the previous call to Execute. The executed commands are stored in the history list. To implement an arbitrary number of undo and redo commands, you need to bypass this list in the opposite and forward directions, invoking the Unexecute or Execute command when you visit each item;
  • want to support change logging so that they can be re-executed after the system crashes. When you add the Save and Load operations to the Command class interface, you can log changes in external memory. To recover from a failure, you will need to load the saved commands from the disk and re-execute them using the Execute operation;
  • it is necessary to structure the system on the basis of high-level operations built from primitive ones. This structure is typical for information systems that support transactions. A transaction encapsulates a data change set. Pattern command allows you to simulate transactions. All teams have a common interface, which makes it possible to work equally with any transactions. With this pattern, you can easily add new types of transactions to the system.

results

  • the command breaks the connection between the object initiating the operation and the object having information on how to perform it;
  • teams are real objects. It is allowed to manipulate and expand them in the same way as in the case of any other

продолжение следует...

Продолжение:


Часть 1 2.4 Patterns of behavior
Часть 2 2.4.9 High Cohesion Pattern - 2.4 Patterns of behavior


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Object oriented programming

Terms: Object oriented programming