![](/user_photo/2706_HbeT2.jpg)
GrandM-Patterns_in_Java
.pdf![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6381x1.jpg)
390 • Глава 8. Поведенческие шаблоны проектирования
return memento ;
1 |
1 сrеаtеМеmелtО ( 5 triлg) |
1 * * |
|
* |
Восстановим состояние этого объекта из данного объекта |
* |
Memen to . |
* 1
void setмemento (Milestone= МementoIF memento) MilestoneМemento= m (МilestoneMemento) memento; mеmеntоМзnаqеr m.mеmеntоМзnаqеr ;
1 1 setMemento (MilestoneMemento)
ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ С ШАБЛОНОМ SNAPSHOT
Command. Шаблон Command позволяет отменять изменения состояния ко манда за командой, не нуждаясь в выполнении моментального снимка полного состояния объекта после каждой команды.
Read-Only Interface. Интерфейсы, выступающие в роли MementoIF, проекти руются при помощи шаблона Read-Only Interface.
Шаблон Observerдавно известен и широко используется. Важно знать о шабло не Observer при работе с уже существующими проектами, использующими этот
шаблон.
Шаблон Observer ранее был описан в работе [GoF95].
СИНОПСИС
Позволяет объектам динамичесЮf регистрировать зависимости между объектами.
В результате объект будет оповещать зависящие от него объекты об изменении
своего состояния.
КОНТЕКСТ
Предположим, что создается ПО дЛЯ компании, которая изготавливает детек
торы дыма, сенсоры движения и другие приборы безопасности. Чтобы с выго
дой использовать преимущества нового рынка, компания планирует ввести но
вую линию устройств. Такие устройства смогут посылать сигнал на карту
безопасности, которая может быть установлена на большинстве компьютеров. Ожидается, что компании, изготавливающие контролирующие системы безо пасности, будут внедрять такие устройства и карты в свои системы. Чтобы об легчить процесс внедрения карт в контролирующие системы, нужно создать
удобный пользовательский API.
АРс ! должен достаточно легко внедряться в программы будущих заказчиков
тем, чтобы они могли получать извещения от карты безопасности. Работа API
не должна требовать от заказчиков изменения архитектуры уже имеющегося у Них ПО. АР! может знать о ПО заказчика только то, что по крайней мере один
ИЛи, возможно, несколько объектов будут иметь метод, который должен вызы ваться при получении извещения от устройства безопасности. На рис. 8.17
Представлен проект такого API.
Экземпляры класса SecurityNotifier получают извещения от карты безо
ПаСНОСТИ и извещают те объекты, которые ранее зарегистрировались для по
Лучения извещений. Только объекты, реализующие интерфейс SecurityObserver,
могут зарегистрироваться у объекта SecurityNoti fier на получение извеще
ний от этого объекта. Объект Securi tyObserver считается зарегистрирован
Нымн на получение извещений от объекта SecurityNoti fier в том случае, если
о
передается методу addObserver объекта SecurityNotifie r. При этом от Меняется регистрация объекта Secur i tyObserver на получение извещений.
Объект SecurityNoti fier передает извещение объекту Securi tyObserver,
Вызывая его метод noti fy. В качестве параметров он передает методу noti fy
observer - 393
РЕШЕНИЕ
На рис. 8. 18 представлена диаграмма классов шаблона Observer.
Очевидно, что эта диаграмма более сложна, чем изображенная на рис. 8.17, ко
торая включает некоторые упрощения, описанные в разделе «Реализация».
|
|
Регистрирует на получение извещений |
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
«interface» |
|
||||||
|
|
|
|
|
|
|
1 |
|
|
|
ObservableIF |
|
|
|
|
|
|
|
|
|
addObserver(:ObserverIF} |
||||||
|
|
|
|
|
|
|
|
|
|
removeObserver(:ObserverIF) |
|||
|
|
|
|
|
|
|
|
|
|
|
|
*" |
|
|
|
|
«interface» |
|
.* |
|
|
|
|
I |
I |
I |
|
|
|
|
ObserverIF |
0. |
|
|
|
|
Observable |
||||
|
|
|
|
|
|
|
|||||||
|
|
|
notify |
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|||||
0. .* |
|
|
|
|
|
Извещает'" |
|
" " " " ""JlMulticaster |
|
||||
|
|
IIII |
|
|
|||||||||
|
|
|
ьII. |
|
|
наблюдения |
1 |
1;'"'щ'" |
|||||
|
|
|
|
|
|
|
|
- |
addObserver(:ObserverIF} |
||||
Observer I |
|
|
|
||||||||||
|
|
|
|||||||||||
|
|
|
|
|
removeObserver(:ObserverIF) |
Рис. 8.18. Шаблон Observer
Опишем роли, исполняемые классами и интерфейсами в шаблоне Observer.
ObserverIF. Интерфейс в этой роли определяет метод, который обычно назы вается noti fy или update. Объект Observable вызывает этот метод для от правки извещения об изменении его состояния, передавая ему любые- необхо
димые аргументы. Во многих случаях ссылка на объект Observable это один
из аргументов, позволяющий методу знать, какой объект отправил извещение.
Observer. Экземпляры классов в этой роли реализуют интерфейс ObserverIF
И получают извещения об изменении состояния от объектов Observable.
ObservabIeIF. Объекты Observable реализуют интерфейс, выступающий в этой Роли. Интерфейс определяет два метода, позволяющие объектам Observer ре
гистрироваться или отменять регистрацию на получение извещений.
ObservabIe. Класс в этой роли реализует интерфейс Obse rvable IF. Его экзем
ПЛяры отвечают за управление регистрацией объектов Observer I F, которые
Хотят получать извещения об изменении состояния. Кроме того, его экземпля
РЫ отвечают за передачу этих извещений. Класс Obse rvable непрямым обра
зом реализует эти обязанности. Вместо этого он делегирует вышеуказанные
Обязанности объекту Multica ster.
Multicaster. Экземпляры класса в этой роли управляют регистрацией объектов Observer I F и передачей им извещений от имени объекта Observable. Суть
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6385x1.jpg)
394 • Глава 8. Поведенческие шаблоны проектирования
этой роли в том, чтобы повысить возможности многократного использования
кода. Делегирование этих обязанностей классу Mul ticaster позволяет много
ратно использовать их реализации всем классам Observable, реализующим
дин и тот же интерфейс ObservableIF, или передавать извещения объектам,
реализующим один и тот же интерфейс Observe rIF.
На рис. 8.19 представлены все случаи взаимодействия меЖдУ объектами, участ
вующими в шаблоне Observer.
1: addObserver(:ObserverIF) |
|
|
|
1.1: addObserver(:ObserverIF) |
|
|
|
||||||||
: :О=Ь=sе=rv:а |
=blе=IF |
|
L: :=M:ulticaster:..-.J |
||||||||||||
|
|
|
|
||||||||||||
|
|
|
IL |
|
|
|
|
|
I |
|
|
|
|||
|
|
|
|
|
|
lti |
|
|
|
|
---:о:ь |
=:sеrvеIr F |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
||||
o:ObservableIF |
2: notify(o) |
:Мu caster |
2.1: notify(o) |
I' |
::: |
|
U |
||||||||
|
|
|
|
|
|
|
|
|
|
L |
:::=- |
|
|||
|
|
|
|
|
|
|
|
|
|
|
..J |
Рис. 8.19. Взаимодействие в шаблоне Observer
Опишем эти взаимодействия.
1.Объекты, реализующие интерфейс ObserverIF, передаются методу addObserver объекта ObservableIF.
1.1. Объект ObservableIF делегирует вызов метода addObserver
связанному с ним объекту Multi caster. Объект Mult icaster добавляет объект ObservableIF в поддерживаемую им коллек цию объектов Observe rIF.
2.Объект Obse rvableIF, помеченный буквой о, должен оповещать дру
гие объекты об изменении своего состояния. Передачу извещения он
инициирует посредством вызова метода noti fy связанного с ним объ
екта Mul ticaster.
2.1.Объект Mul ticaster вызывает метод not i f y каждого из объех
тов ObserverIF, принадлежащих его коллекции.
РЕАЛИЗАЦИЯ
Н а бл ю д е н и е з а о бъ е кто м O b se гva b I e
Объект Obse rvable в качестве параметра метода noti fy объекта Observer
обычно передает ссьmку на самого себя. В большинстве случаев объект Observerа
цолжен обращаться к атрибутам объекта Observable С целью реагирования н
•извещения. Опишем несколько способов предоставления такого доступа.
Добавление методов в интерфейс ObservableIF для считывания значениЙ атрибутов. Как правило, это самое лучшее решение. Однако оно работает
Observer - 395
только в том случае, если все классы, реализующие интерфейс Obser vable IF, имеют общий набор атрибутов, достаточный ДЛЯ того, чтобы объ
•екты Obs erver могли воздействовать на извещения.
Может быть несколько интерфейсов Observable I F, каждый из которых будет обеспечивать доступ к такому количеству атрибутов, которое является достаточным для воздействия объектов Observer на извещения. С этой це лью интерфейсы Obs erverIF должны объявлять некоторую версию своего метода not i fy ДЛЯ каждого из интерфейсов ObservableIF. Однако при требовании от объектов наблюдателя осведомленности о множестве интер фейсов существование интерфейса ObservableIF во многом теряет свой изначальный смысл. Требовать от класса осведомленности о множестве ин терфейсов и о множестве классов - почти одно и то же, поэтому такое ре
•шение проблемы - не самый лучший вариант.
Можно передать атрибуты, необходимые объектам ObserverIF, в качестве параметров их методам noti fy. Основным недостатком данного решения является то, что в этом случае объекты Obse rvable должны иметь доста точную информацию об объектах Observe rIF, позволяющую предостав
лять им правильные значения атрибутов. Если набор атрибутов, необходи мых объектам Obse rverIF, изменяется, то должны соответствующим обра
•зом изменяться все классы Observable.
Существует возможность вообще обойтись без интерфейса ObservableIF
ипередать объекты Observable объектам Observer I F в качестве экземп ляров их реального класса. Это подразумевает перезагрузку метода noti fy интерфейса ObserverI F таким образом, что для каждого класса Obser vable, передающего извещения объектам ObserverIF, определяется свой метод noti fy.
Основной недостаток такого подхода состоит в том, что классы Observer должны быть осведомлены о классах Observable , которые будут передавать извещения их экземплярам, и должны знать, как считывать из них необходи
мые им атрибуты. Но с другой стороны, если только один класс Observable
будет передавать извещения классам Observer, то такое решение будет являться
самым лучшим. Оно не приведет к усложнению каких-либо классов. Зависи
Мость от единственного интерфейса заменяется зависимостью от единственного КЛасса. Проект становится проще благодаря устранению интерфейса Obser VableIF.
В примере, описанном в разделе «Контекст» , используется такое упрощенное
решение.
От ка з от кл а сс а M u l ti c a ster
Другим распространенным упрощением шаблона Observer является устранение
КЛасса Ми!ticaster. Если класс Observable представляет собой единственный
КЛасс, передающий извещения объектам, реализующим определенный интер
Фейс, то нет никакой необходимости в классе Multicaster. Именно поэтому
396 • Глава 8. Поведенческие шаблоны проектирования
пример, описанный в разделе «Контекст» , не содержит класс, выступающий
в роли Mul ticaste r. Другая причина отказа от класса Mul t icaster состоит в том, что объект Observable всегда должен передавать извешения только oд
ному объекту. В таком случае управление и передача извещений объектам
Observer настолько проста, что наличие класса Mu l ticaster скорее усложня
ет, чем упрошает проект.
П а к ет и р о в а н и е и з в е ще н и й
Иногда нет необходимости или пользы в том, чтобы извешать объекты Obser ve r о каждом изменении объекта Observable. В таком случае можно избежать
передачи ненужных извещений, создавая пакет изменений состояния и откла
дывая отправку извещений до тех пор, пока не заполнится весь этот пакет из
менений состояния. Если состояние объекта Obse rvable изменяется другим объектом, то представление единственного извещения в виде пакета измене
ний может вызвать затруднения. Нужно добавить в класс объекта Observable метод, который может быть вызван другими объектами для указания начала па
кета изменения состояния. Если изменение состояния является только частью пакета, то объект не должен передавать какие либо извещения своим зарегист рированным наблюдателям. Кроме того, нужно добавить в классДЛЯобъекта Observable метод, который может быть вызван другими объектами указа ния конца пакета изменений состояния. Если при вызове этого метода с мо мента начала пакета происходили какие либо изменения состояния, объект должен передать извещения своим зарегистрированным наблюдателям.
Если изменение состояния объекта Observable инициируется несколькими объектами, то определение конца пакета изменений может быть более слож ным. Эффективный способ решения данной проблемы заключается в том,
чтобы ввести дополнительный объект, который должен координировать изме
нения состояния, инициируемые другими объектами, и достаточно хорошо по
нимать их логику с тем, чтобы определять конец пакета изменений. Более
подробное описание использования одного объекта для координирования дей
ствий других объектов можно найти в разделе, посвященном шаблону Mediator.
За п р ет
Обычно шаблон Observer используется для оповещения других объектов об из
менении состояния некоторого объекта. Широко распространенная версия
этого шаблона состоит в том, что определяется альтернативный интерфейс
Observab leIF, который позволяет объектам выдавать запрос на получение из
вешения, перед тем как изменяется состояние объекта. Как правило, если изве
щения об изменении состояния отправляются после изменения состояния, ЭТО
делается с той целью, чтобы разрещить передачу изменения другим объектам.
Если извещение отправляется перед изменением состояния, то другие объектЫ
могут запретить изменение состояния. Чаше всего при реализации такого за прета объект должен генерировать исключение, что и препятствует предпола гаемому изменению состояния объекта.
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6388x1.jpg)
Observer 8 397
СЛЕДСТВИЯ
Шаблон ObselVer позволяет объекту передавать извещения другим объектам та ким образом, что ни отправитель, ни получатель извещений ничего не знают о классах друг друга.
Существуют некоторые ситуации, когда использование шаблона ObselVer мо жет• привuдить к непредвиденным и нежелательным результатам.
• Более серьезная проблема связана с циклическими зависимостями. Объек ты вызывают методы noti fy друг друта до тех пор, пока не переполнится стек и не будет сгенерирована ошибка StackOverflowError. Несмотря на всю серьезность этой проблемы, она может быть легко решена посредством задания внутреннего флага в одном из классов, участвующих в цикле. Такой флаг обозначает рекурсивное извещение, например:
private boolean inNotify = false ; puыlcc void notify (ObservableIF source)
if (inNotify) return;=
inМotify true ;
inNotify = false ;
•Если извещение может быть передано асинхронно по отношению к другим потокам (как в случае, описанном в примере раздела «(Контекст» ), то следу
ет рассмотреть некоторые дополнительные последствия. Механизм асин
хронной передачи извещений должен обеспечивать согласованность объек тов, получающих извещения. Важно также, чтобы извещение не блокирова
ло другой ожидающий поток.
Когда объект Observer получает извещение, он знает, какой объект изменил
ся, но он не знает, каким образом он изменился. Объект Observer не должен
определять, какие атрибуты объекта ObservableIF изменились. Наблюдателю,
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6389x1.jpg)
![](/html/2706/346/html_MxkKzTLKJz.Ja4Q/htmlconvd-LkZgZ6390x1.jpg)
Observer • 399
public void addObserver (SecurityObserver observer) { observers . add (observer) ;
}/ / addObserver ( Securi tyObserve r)
public void removeObserver (SecurityObserver observer) { observers . remove (observer) ;
} / / |
removeObserve r ( Securi tyObserve r ) |
|
private void notify (int device , |
int event) |
|
Iterator iterator = observers . iterator ( ) ; |
||
while (iterator . hasNext ( » |
{ |
|
|
« SecurityObserver) iterator . next (» . notify (device, |
|
|
event) ; |
|
|
/ / whi l e |
|
/ / |
not i fy ( i n t , i n t ) |
|
/ / class Secu rityNotifier
иri наконец, пример класса-anаптера, позволяюший экземплярам класса Secu ni tyMonitor получать извешения даже в том случае, когда класс SecurityMo
tor не реализует класс SecurityObserver.
class SecurityAdapter implements SecurityObserver private SecurityMonitor зт;
SecurityAdapter= (SecurityMonitor зт) this . sm зт;
} / / Con s t ructor ( Secu rityMonitor)
/* *
* Этот метод вызывается для передачи извещения ,
* свя занного с безопасностью .
* @pa ram device
* Идентификатор устройства , от которого получено это
*извещение .
*@param event
*Одна из вь еупомянутых констант .
*/
public void notify (int device, int event) switch (event)ALAR М : (
сазе
sm. securityAlert (device) ;