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.
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.
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.
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.
results
The iterator pattern has the following important features:
Figure 2.23 Iterator Pattern Structure
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.
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.
Figure 2.25 Hierarchy class of window widgets using an intermediary
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
Figure 2.27 Structure of the Mediator pattern.
results
The mediator pattern has the following advantages and disadvantages:
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
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:
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.29 Структура паттерна State (пример)
При каждом изменении состояния соединения TCPConnection изменяет свой объект-состояние. Например, когда установленное соединение закрывается, TCPConnection заменяет экземпляр классаTCPEstablished экземпляром TCPClosed.
Паттерн State имеет смысл использовать в следующих ситуациях:
Structure
Рисунок 2.30 Структура паттерна State
results
Локализует зависящее от состояния поведение и делит его на части, соответствующие состояниям. Паттерн состояние помещает все поведение, ассоциированное с конкретным состоянием, в отдельный объект. Поскольку зависящий от состояния код целиком находится в одном из подклассов класса State, то добавлять новые состояния и переходы можно просто путем порождения новых подклассов. Вместо этого можно было бы использовать данные-члены для определения внутренних состояний, тогда операции объекта Context проверяли бы эти данные. Но в таком случае похожие условные операторы или операторы ветвления были бы разбросаны по всему коду класса Context. При этом добавление нового состояния потребовало бы изменения нескольких операций, что затруднило бы сопровождение. Паттерн состояние позволяет решить эту проблему, но одновременно порождает другую, поскольку поведение для различных состояний оказывается распределенным между несколькими подклассами State. Это увеличивает число классов. Конечно, один класс компактнее, но если состояний много, то такое распределение эффективнее, так как в противном случае пришлось бы иметь дело с громоздкими условными операторами. Наличие громоздких условных операторе в нежелательно, равно как и наличие длинных процедур. Они слишком монолитны, вот почему модификация и расширение кода становится проблемой. Паттерн состояние предлагает более удачный способ структурирования зависящего от состояния кода. Логика, описывающая переходы между состояниями, больше не заключена в монолитные операторы if илиswitch, а распределена между подклассами state. При инкапсуляции каждого перехода и действия в класс состояние становится полноценным объектом. Это улучшает структуру кода и проясняет его назначение;
Делает явными переходы между состояниями. Если объект определяет свое текущее состояние исключительно в терминах внутренних данных, то переходы между состояниями не имеют явного представления; они проявляются лишь как присваивания некоторым переменным. Ввод отдельных объектов для различных состояний делает переходы более явными. Кроме того, объекты State могут защитить контекст Context от рассогласования внутренних переменных, поскольку переходы с точки зрения контекста - это атомарные действия. Для осуществления перехода надо изменить значение только одной переменной (объектной переменной State в классе Context), а не нескольких;
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:
Всех этих проблем можно избежать, если определить классы, инкапсулирующие различные алгоритмы разбиения на строки. Инкапсулированный таким образом алгоритм называется стратегией.
Рисунок 2.31 Структура паттерна Strategy (пример)
Предположим, что класс Composition отвечает за разбиение на строки текста, отображаемого в окне программы просмотра, и его своевременное обновление. Стратегии разбиения на строки определяются не в классе Composition, а в подклассах абстрактного класса Compositor. Это могут быть, например, такие стратегии:
Объект Composition хранит ссылку на объект Compositor. Всякий раз, когда объекту Composition требуется переформатировать текст, он делегирует данную обязанность своему объекту Compositor. Клиент указывает, какой объект Compositor следует использовать, параметризуя им объект Composition.
Используйте паттерн Стратегия, когда:
Structure and participants
Рисунок 2.32 Структура паттерна Strategy
results
У паттерна стратегия есть следующие достоинства и недостатки:
void Composition::Repair ()
{
switch (_breakingStrategy) {
case SimpleStrategy:
ComposeWithSimpleCompositor();
break;
case TeXStrategy:
ComposeWithTeXCorapositor();
break;
// ...
}
// если необходимо, объединить результаты
// с имеющейся композицией
}
Паттерн стратегия позволяет обойтись без оператора переключения за счет делегирования задачи разбиения на строки объекту Strategy:
void Composition::Repair ()
{
_compositor->Compose();
// если необходимо, объединить результаты
// с имеющейся композицией
}
Если код содержит много условных операторов, то часто это признак того, что нужно применить паттерн стратегия;
Title
Template Method (шаблонный метод).
Task
Шаблонный метод определяет основу алгоритма и позволяет подклассам переопределить некоторые шага алгоритма, не изменяя его структуру в целом.
Motivation
Рассмотрим каркас приложения, в котором имеются классы Application Document. Класс Application отвечает за открытие существующих документов, хранящихся во внешнем формате, например в виде файла. Объект класса Document представляет информацию документа после его прочтения из файла.
Приложения, построенные на базе этого каркаса, могут порождать подклассы от классов Application и Document, отвечающие конкретным потребностям. Например, графический редактор определит подклассы DrawApplication и DrawDocument, а электронная таблица - подклассы SpreadsheetApplication и SpreadsheetDocument.
Рисунок 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.34 Структура паттерна Template Method
results
Шаблонные методы - один из фундаментальных приемов повторного использования кода. Они особенно важны в библиотеках классов, поскольку предоставляют возможность вынести общее поведение в библиотечные классы.
Шаблонные методы приводят к инвертированной структуре кода, которую иногда называют принципом Голливуда, подразумевая часто употребляемую в этой кино-империи фразу «Не звоните нам, мы сами позвоним». В данном случае это означает, что родительский класс вызывает операции подкласса, а не наоборот.
Шаблонные методы вызывают операции следующих видов:
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
}
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.
Figure 2.35 Chain of Responsibility pattern. Relationships between objects
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.
Figure 2.37 The structure of the Chain of Responsibility pattern (example)
Applicability
Structure
Figure 2.38 The structure of the Chain of Responsibility pattern
results
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.
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.
Figure 2.40. Command Pattern Macro.
Applicability
results
продолжение следует...
Часть 1 2.4 Patterns of behavior
Часть 2 2.4.9 High Cohesion Pattern - 2.4 Patterns of behavior
Comments
To leave a comment
Object oriented programming
Terms: Object oriented programming