Э. Гамма Р. Хелм Р. Джонсон Дж. Влиссидес

Вид материалаДокументы

Содержание


Паттерны поведения
Паттерны поведения
Классы Colleague
Пример кода
Паттерн Mediator
Известные применения
Паттерн Memento
Известен также под именем
Паттерн Memento
Паттерны поведения
Паттерны поведения
Подобный материал:
1   ...   12   13   14   15   16   17   18   19   20

^ Паттерны поведения



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

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

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

Так, класс FontDialogDirector может служить посредником между вид­жетами в диалоговом окне. Объект этого класса «знает» обо всех виджетах в окне




Паттерн Mediator

и координирует взаимодействие между ними, то есть выполняет функции центра коммуникаций.

На следующей диаграмме взаимодействий показано, как объекты коопериру­ются друг с другом, реагируя на изменение выбранного элемента списка.



Последовательность событий, в результате которых информация о выбран­ном элемента списка передается в поле ввода, следующая:
  1. Список информирует распорядителя о происшедших в нем изменениях.
  2. Распорядитель получает от списка выбранный элемент.
  3. Распорядитель передает выбранный элемент полю ввода.
  4. Теперь, когда поле ввода содержит какую-то информацию, распорядитель ак­
    тивизирует кнопки, позволяющие выполнить определенное действие (напри­
    мер, изменить шрифт на полужирный или курсив).

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

Абстракцию FontDialogDirector можно было бы интегрировать в библио­теку классов так, как показано на рисунке.




^ Паттерны поведения

DialogDirector - это абстрактный класс, который определяет поведение диалогового окна в целом. Клиенты вызывают его операцию ShowDialog для отоб­ражения окна на экране. CreateWidgets - это абстрактная операция для созда­ния виджетов в диалоговом окне. WidgetChanged - еще одна абстрактная опе­рация; с ее помощью виджеты сообщают распорядителю об изменениях. Подклассы DialogDirector замещают операции CreateWidgets (для создания нужных виджетов) и WidgetChanged (для обработки извещений об изменениях).

Применимость

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

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

а нельзя повторно использовать объект, поскольку он обменивается информа­цией со многими другими объектами;

а поведение, распределенное между несколькими классами, должно подда­ваться настройке без порождения множества подклассов.

Структура



Типичная структура объектов.



Участники

Mediator (DialogDirector) - посредник;

- определяет интерфейс для обмена информацией с объектами Col league;


Паттерн Mediator

a ConcreteMediator (FontDialogDirector) - конкретный посредник:

— реализует кооперативное поведение, координируя действия объектов
Colleague;

- владеет информацией о коллегах и подсчитывает их;
а ^ Классы Colleague (ListBox, EntryField) - коллеги:

— каждый класс Colleague «знает» о своем объекте Mediator;

- все коллеги обмениваются информацией только с посредником, так как
при его отсутствии им пришлось бы общаться между собой напрямую.

Отношения

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

Результаты

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

а снижает число порождаемых подклассов. Посредник локализует поведение, которое в противном случае пришлось бы распределять между нескольки­ми объектами. Для изменения поведения нужно породить подклассы толь­ко от класса посредника Mediator, классы коллег Colleague можно ис­пользовать повторно без каких бы то ни было изменений;

а устраняет связанность между коллегами. Посредник обеспечивает слабую связанность коллег. Изменять классы Colleague и Mediator можно неза­висимо друг от друга;

а упрощает протоколы взаимодействия объектов. Посредник заменяет дис­циплину взаимодействия «все со всеми» дисциплиной «один со всеми», то есть один посредник взаимодействует со всеми коллегами. Отношения вида «один ко многим» проще для понимания, сопровождения и расширения;

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

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

Реализация

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

а избавление от абстрактного класса Mediator. Если коллеги работают только с одним посредником, то нет необходимости определять абстрактный класс Mediator. Обеспечиваемая классом Mediator абстракция позволяет кол­легам работать с разными подклассами класса Mediator и наоборот;


Паттерны поведения

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

Другой подход: в классе Mediator определяется специализированный ин­терфейс уведомления, который позволяет коллегам обмениваться инфор­мацией более свободно. В Smalltalk/V для Windows применяется некоторая форма делегирования: общаясь с посредником, коллега передает себя в ка­честве аргумента, давая посреднику возможность идентифицировать отпра­вителя. Об этом подходе рассказывается в разделе «Пример кода», а о реа­лизации в Smalltalk/V - в разделе «Известные применения».

^ Пример кода

Для создания диалогового окна, обсуждавшегося в разделе «Мотивация», воспользуемся классом DialogDirector. Абстрактный класс DialogDirector

определяет интерфейс распорядителей:

class DialogDirector { public:

virtual -DialogDirector() ;

virtual void ShowDialogf) ;

virtual void WidgetChanged(Widget*) = 0;

protected:

DialogDirector();

virtual void CreateWidgets() = 0; };

Widget - это абстрактный базовый класс для всех виджетов. Он располагает информацией о своем распорядителе:

class Widget { public:

Widget(DialogDirector*);

virtual void Changed();

virtual void HandleMouse(MouseEventk event);

// private:

DialogDirector* _director;

};

Changed вызывает операцию распорядителя WidgetChanged. С ее помощью виджеты информируют своего распорядителя о происшедших с ними изменениях:

^ Паттерн Mediator

void Widget::Changed () {

_director->WidgetChanged(this) ,

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

ListBox, Entry-Field и Button - это подклассы Widget для специализиро­ванных элементов интерфейса. В классе ListBox есть операция GetSelect ion для получения текущего множества выделенных элементов, а в классе Entry-Field - опе­рация SetText для помещения текста в поле ввода:

class ListBox : public Widget { public:

ListBox(DialogDirector*);

virtual const char* GetSelectionf);

virtual void SetList(List* listltems);

virtual void HandleMouse(MouseEvent& event);


class EntryField : public Widget { public:

EntryField(DialogDirector*) ;

virtual void SetText(const char* text);

virtual const char* GetText();

virtual void HandleMouse(MouseEvent& event),


Операция Changed вызывается при нажатии кнопки Button (простой вид­жет) . Это происходит в операции обработки событий мыши HandleMouse:

class Button : public Widget { public:

Button(DialogDirector*) ;

virtual void SetText(const char* text); virtual void HandleMouse(MouseEvent& event);

void Button::HandleMouse (MouseEvent& event) { // ■ ■ ■ Changed();

Паттерны поведения

Класс FontDialogDirector является посредником между всеми виджетами в диалоговом окне. FontDialogDirector - это подкласс класса DialogDirector:

class FontDialogDirector : public DialogDirector { public:

FontDialogDirector();

virtual -FontDialogDirector();

virtual void WidgetChanged(Widget*);

protected:

virtual void CreateWidgets();

private:

Button* _ok; Button* _cancel; ListBox* _fontList ; EntryField* _fontNarae;

FontDialogDirector отслеживает все виджеты, которые ранее поместил в ди­алоговое окно. Переопределенная в нем операция CreateWidgets создает вид­жеты и инициализирует ссылки на них:

void FontDialogDirector::CreateWidgets (} { _ok = new Button(this); _cancel = new Button(this); _fontList = new ListBox(this) ; _fontName = new EntryField(this);

// поместить в список названия шрифтов

// разместить все виджеты в диалоговом окне

Операция WidgetChanged обеспечивает правильную совместную работу виджетов:

voidFontDialogDirector::WidgetChanged ( Widget* theChangedWidget

if (theChangedWidget == _fontList) {

_fontName->SetText(_fontList->GetSelection())

} else if (theChangedWidget == _ok) {

// изменить шрифт и уничтожить диалоговое окно

} else if (theChangedWidget == _cancel) { // уничтожить диалоговое окно


Паттерн Mediator

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

^ Известные применения

И в ЕТ++ [WGM88], и в библиотеке классов THINK С [Sym93b] применяются похожие на нашего распорядителя объекты для осуществления посредничества между виджетами в диалоговых окнах.

Архитектура приложения в Smalltalk/V для Windows основана на структуре посредника [LaL94]. В этой среде приложение состоит из окна Window, которое содержит набор панелей. В библиотеке есть несколько предопределенных объек­тов-панелей Рапе, например: TextPane, ListBox, Button и т.д. Их можно ис­пользовать без подклассов. Разработчик приложения порождает подклассы только от класса ViewManager (диспетчер видов), отвечающего за обмен информацией между панелями. ViewManager - это посредник, каждая панель «знает» своего диспетчера, который считается «владельцем» панели. Панели не ссылаются друг на друга напрямую.

На изображенной диаграмме объектов показан мгновенный снимок работаю­щего приложения.



В Smalltalk/V для обмена информацией между объектами Рапе и ViewManager используется механизм событий. Панель генерирует событие для получения дан­ных от своего посредника или для информирования его о чем-то важном. С каж­дым событием связан символ (например, #select), который однозначно его идентифицирует. Диспетчер видов регистрирует вместе с панелью селектор метода, который является обработчиком события. Из следующего фрагмента кода видно, как объект ListPane создается внутри подкласса ViewManager и как ViewManager регистрирует обработчик события #select:

self addSubpane: (ListPane new paneName: 'myListPane' ; owner: self; when: #select perform: #listSelect:) .


Паттерны поведения

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

Аналогичным образом посредник применяется в графических редакторах Unidraw [VL90], где используется класс CSolver, следящий за соблюдением огра­ничений связанности между коннекторами. Объекты в графических редакторах могут быть визуально соединены между собой различными способами. Коннекто­ры полезны в приложениях, которые автоматически поддерживают связанность, например в редакторах диаграмм и в системах проектирования электронных схем. Класс CSolver является посредником между коннекторами. Он разрешает огра­ничения связанности и обновляет позиции коннекторов так, чтобы отразить изме­нения.

Родственные паттерны

Фасад отличается от посредника тем, что абстрагирует некоторую подсисте­му объектов для предоставления более удобного интерфейса. Его протокол одно­направленный, то есть объекты фасада направляют запросы классам подсисте­мы, но не наоборот. Посредник же обеспечивает совместное поведение, которое объекты-коллеги не могут или не «хотят» реализовывать, и его протокол двуна­правленный.

Коллеги могут обмениваться информацией с посредником посредством пат­терна наблюдатель.

^ Паттерн Memento

Название и классификация паттерна

Хранитель - паттерн поведения объектов.

Назначение

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

^ Известен также под именем

Token (лексема).

Мотивация

Иногда необходимо тем или иным способом зафиксировать внутреннее состоя­ние объекта. Такая потребность возникает, например, при реализации контрольных точек и механизмов отката, позволяющих пользователю отменить пробную опера­цию или восстановить состояние после ошибки. Его необходимо где-то сохранить, чтобы позднее восстановить в нем объект. Но обычно объекты инкапсулируют все


^ Паттерн Memento

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



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

Система разрешения ограничений - хорошо известный способ поддержания связанности между объектами. Ее функции могут выполняться объектом класса ConstraintSolver, который регистрирует вновь создаваемые соединения и ге­нерирует описывающие их математические уравнения. А когда пользователь ка­ким-то образом модифицирует диаграмму, объект решает эти уравнения. Результа­ты вычислений объект ConstraintSolver использует для перерисовки графики так, чтобы были сохранены все соединения.



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

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

Паттерн хранитель поможет решить данную проблему. Хранитель — это объект, в котором сохраняется внутреннее состояния другого объекта - хозяина храните­ля. Для работы механизма отката нужно, чтобы хозяин предоставил хранитель, когда возникнет необходимость записать контрольную точку состояния хозяина. Только хозяину разрешено помещать в хранитель информацию и извлекать ее от­туда, для других объектов хранитель непрозрачен.

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

1. Редактор запрашивает хранитель у объекта ConstraintSolver в процессе

выполнения операции перемещения.

2. ConstraintSolver создает и возвращает хранитель, в данном случае эк­
земпляр класса SolverState..Хранитель Solver State содержит структуры


^ Паттерны поведения

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

Такая организация позволяет объекту ConstraintSolver «знакомить» дру­гие объекты с информацией, которая ему необходима для возврата в предыдущее состояние, не раскрывая в то же время свою структуру и представление.

Применимость

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

а необходимо сохранить мгновенный снимок состояния объекта (или его части), чтобы впоследствии объект можно было восстановить в том же состоянии;

а прямое получение этого состояния раскрывает детали реализации и нару­шает инкапсуляцию объекта.

Структура



Участники

a Memento (SolverState) - хранитель:
  • сохраняет внутреннее состояние объекта Originator. Объем сохраняе­
    мой информации может быть различным и определяется потребностями
    хозяина;
  • запрещает доступ всем другим объектам, кроме хозяина. По существу,
    у хранителей есть двалнтерфейса. «Посыльный» Caretaker «видит»
    лишь «z/зкмм» интерфейс хранителя - он может только передавать храни­
    теля другим объектам. Напротив, хозяину доступен «широкий» интер­
    фейс, который обеспечивает доступ ко всем данным, необходимым для
    восстановления в прежнем состоянии. Идеальный вариант - когда толь­
    ко хозяину, создавшему хранитель, открыт доступ к внутреннему состоя­
    нию последнего;

a Originator (ConstraintSolver) - хозяин:
  • создает хранитель, содержащего снимок текущего внутреннего состояния;
  • использует хранитель для восстановления внутреннего состояния;

Паттерн Memento

a Caretaker (механизм отката) - посыльный:
  • отвечает за сохранение хранителя;
  • не производит никаких операций над хранителем и не исследует его внут­
    реннее содержимое.

Отношения

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




Иногда этого не происходит, так как последнему не нужно восстанавливать

прежнее состояние;

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

Результаты

Характерные особенности паттерна хранитель:

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

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

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


^ Паттерны поведения

объем информации для занесения в память хранителя или если клиенты со­здают и возвращают хранителей достаточно часто. Если плата за инкапсуля­цию и восстановление состояния хозяина велика, то этот паттерн не всегда подходит (см. также обсуждение инкрементности в разделе «Реализация»);

а определение «узкого» и «широкого» интерфейсов. В некоторых языках слож­но гарантировать, что только хозяин имеет доступ к состоянию хранителя;

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

Реализация

При реализации паттерна хранитель следует иметь в виду:

а языковую поддержку. У хранителей есть два интерфейса: «широкий» для хозяев и «узкий» для всех остальных объектов. В идеале язык реализации должен поддерживать два уровня статического контроля доступа. В C++ это возможно, если объявить хозяина другом хранителя и сделать закрытым «широкий» интерфейс последнего (с помощью ключевого слова private). Открытым (public) остается только «узкий» интерфейс. Например:

class State;

class Originator { public:

Memento* CreateMemento();

void SetMemento(const Memento*);

// ... private :

State* _state; // внутренние структуры данных // ■ ■ ■

class Memento { public:

// узкий открытый интерфейс

virtual ~Memento(); private:

// закрытые члены доступны только хозяину Originator

friend class Originator;

Memento() ;

void SetState(State*); State* GetState() ;

private:

State* state;