Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

GrandM-Patterns_in_Java

.pdf
Скачиваний:
98
Добавлен:
14.03.2016
Размер:
8.88 Mб
Скачать

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

изменении его состояния.

92

Глава 8.

П

оведенческие шаблоны проектирования

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

SecurityNotifier

 

 

 

 

о....

 

«interface»

 

 

 

 

 

 

 

 

 

Извещает

 

SecurityObserver

 

addObserver(:SecurityObserver)

 

-

 

 

 

 

 

ALARM:int - 1 {frozen}

 

removeObserver(:SecurityObserver)

 

 

 

 

 

LOW]OWER:int - 2 {frozen}

 

..

.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

DIAGNOSТIC:int- 3 {frozen}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

SecurityMonitor ---------SecurityAdaptert

 

se UrityCLient]

 

 

 

 

 

 

 

 

 

...Извещает

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

1

1 '----------'

 

 

 

 

 

 

 

Рис. 8.17. API, предназначенный ДЛЯ извещений системы безопасности

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

вшее исходное извещение, и число, задающее тип извещения.

Остальные классы, изображенные на диаграмме, не имеют отношения к API.

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

как Secur i tyCl ient, может обозначать любой класс, который заказчик до­ бавляетi к своему контролирующему ПО и который реализует интерфейс Secur tyObserver . Потребители могут добавлять такие классы к своему контролирующему ПО дЛЯ обработки извещений, поступивших от объекта

Securi tyNoti f ier .

Класс, указанный на диаграмме как Secur ityMoni tor, соответствует любому классу, имеющемуся в контролирующем ПО заказчика и не реализующему ин­ терфейс SecurityObserver, но содержащему метод, который должен вызы­ ваться для обработки извещений от устройств безопасности. Потребитель мо­ жет заставить экземпляры такого класса получать извещения, не изменяя сам

класс. С этой целью потребитель может написать класс-адаптер, реализующий интерфейс SecurityObserver таким образом, что его метод not i fy будет вы­

зывать соответствующий метод класса SecurityMonitor.

моти в ы

©Реализуются два разных независимых класса. Экземпляр одного такого

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

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

связанный с ним объект изменяет состояние. Однако эти два класса не

должны работать друг с другом. Чтобы быть многократно используемыми,

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

©Есть отношение зависимости <<один к нескольким» , которое может потр06е­

® Для передачи извещений и задания их приоритета нужна некоторая логика.

Эта логика не зависит ни от отправителя, ни от получателя извещений.

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. Суть

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, который позволяет объектам выдавать запрос на получение из­

вешения, перед тем как изменяется состояние объекта. Как правило, если изве­

щения об изменении состояния отправляются после изменения состояния, ЭТО

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

Если извещение отправляется перед изменением состояния, то другие объектЫ

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

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

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 изменились. Наблюдателю,

398 Глава 8. Поведенческие шаблоны проектирования

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

ПРИМЕНЕНИЕ В JAVA API

Модель делегирования событий в языке Java - это специальная форма шабло­

на Observer. Классы, экземпляры которых могут быть источниками событий,

выступают в роли Observable. Интерфейсы получателей событий исполняют

роль ObserverIF. Классы, реализующие интерфейсы получателей событий,

играют роль Obse rver. Поскольку существует целый ряд классов, передающих

различные подклассы класса j ava . awt . AwtEvent своим получателям, то су­ ществует используемый ими класс в роли Mul ticaster, имя которого - j ava . awt . AWTEventMulticaster.

ПРИМЕР КОДА

Следующий код реализует некоторую часть проекта контроля безопасности, представленного в разделе «Контекст» . Первый фрагмент кода - это интер­ фейс Securi tyObserver . Чтобы экземпляры некоторого класса могли полу­ чать извещения, он должен реализовывать интерфейс Securi tyObserver.

public interface SecurityObserverALARМ ( public final int = 1 ; public final int LOW_POWER -=23; ; public final int DIAGNOSTIC

/* *

* Этот метод вызывается для передачи извещения

*от устройства безопасности .

*@param device

*

Идентификтор устройства , от которого получено это

*извещение .

*@param event

*Одна из вышеупомянутых констант .

* / ic void notify (int device , int event) ; publ

/ / interface SecurityObserver

Следующий фрагмент кода представляет собой класс SecurityNotifier, оТ­

вечающий за передачу извещений, которые компьютер получает от устройсТВ системы безопасности.

class SecurityNotifier

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]