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

GrandM-Patterns_in_Java

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

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

/* *

* Если данный объект становится текущим состоянием, * вызывается этот метод .

* /

protected void enter () apply . setEnabled(true) ; revert . setEnabled (true) ; save . setEnabled(true) ;

// enter

// class BothDi rty

// class DirtyState

ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ С ШАБЛОНОМ STATE

F1yweight. Шаблон FIyweight можно применять для совместного использова­ ния объектов состояний.

Mediator. При реализации пользовательских интерфейсов шаблон State часто применяется вместе с шаблоном Mediator.

Singleton. Непараметрические состояния можно реализовывать с помощью шаблона Singleton.

Polymorphism. Проект операций, зависящих от состояний, реализован с исполь­ зованием конкретных классов состояний, соответствующих шаблону Polymor­ phism, рассмотренному в книге [Grand99].

Этот шаблон ранее бьUI описан в работе [Woolf97].

СИНОПСИС

Шаблон Null Object представляет собой альтернативу использованию null для указания отсутствия объекта, которому делегируется операция. Применение null для указания отсутствия TaKoro объекта требует проверки на null перед

каждым вызовом методов объекта. Вместо использования null шаблон Null

Object вводит ссылку на объект, который ничего не делает.

КОНТЕКСТ

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

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

Организовать это можно просто: определить интерфейс под названием

Warni ngRouter, а затем написать классы, которые делегируют передачу преду­ преждений объектам, реализующим этот интерфейс (рис. 8.23).

 

 

:t-:l;-

 

 

 

 

 

 

 

 

 

I

 

_испол_ьэу=- _ет_ _

---

;;

«interface»

 

_----I

BusinessRuLe

_w::.::аr.П:.:i.:,,:,:Пg.2.R..оute.::.r:

 

 

 

_

_

_ 1

 

routeWarning(warning:String):booLean

 

 

 

 

 

 

 

I

 

 

I

 

 

 

 

 

 

 

I WarningDiaLog I

I WarningLogger I

Рис. 8.23. Интерфейс WaгningRouter

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

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

ССЬUIалась бы на объект WarningRouter, содержащий null . Применение такого

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

соблена к их изменению.

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

механизма означает, что перед тем, как объект Bus inessRule сможет вьщать

предупреждающее сообщение, он сначаладолжен проверить, не равна ли пере­

менная nul l . В зависимости от конкретного класса бизнес-правила только одно или несколько мест могут ссылаться на объект WarningRouter. Сущест­

вуют процедурные способы, ограничивающие дополнительную сложность,

связанную с выполнением таких проверок на nul l . Однако при каждом обра­

щении к методам объекта WarningRouter кто-то может забыть задать в исход­

ном коде проверку на nul l , и тогда на стадии выполнения будет сгенерировано

исключение Nul l PointerException.

Альтернативой использованию nu l l ДЛЯ указания отсутствия действий служит

создание класса, реализующего WarningRoute r и ничего не делающего с пре­

дупреждающим сообщением. Этот класс показан на рис. 8.24.

 

_

 

 

 

 

 

 

 

__ ____ «interface»

 

 

I

BusinessRuLe :r.1:- -П,и-;,с..- О:;ЛеЬ..:Э:;..Ус т:

 

 

=wаr,,:,,:П.i::пg..R::о.u:::.:.ter __----I

 

 

1 routeWarning(warning:String):booLean

 

 

 

 

 

 

 

 

 

 

I IgnoreWarning I I WarningDiaLog I I WarningLogger I

 

Рис. 8.24.

Класс, игнорирующий предупреждение

Преимущество существования класса IgnoreWarning состоит в том, что его можно использовать точно так же, как и любые другие классы, реализующие интерфейс WarningRouter. Он не нуждается в проверке на nul l или в любой другой специальной логике.

МОТИВЫ

©Класс делегирует операцию другому классу. Делегирующий класс обычно

не заботится о том, каким образом другой класс реализует эту операцию.

Однако иногда он требует, чтобы операция была реализована как бездейст­

вующая.

©Нужно, чтобы класс, делегирующий операцию, делегировал ее в любом слу­

чае, включая случай отсутствия действий. Нежелательно, чтобы ситуациЯ

отсутствия действий нуждалась в каком-либо специальном коде.

РЕШЕНИЕ

На рис. 8.25 представлена диаграмма классов, демонстрирующая струКТУРУ

шаблона Null Object.

Опишем роли, исполняемые классами и интерфейсом, участвующим в этоМ шаблоне.

Null Object 8 413

 

 

 

Использует

Delegator

. . J l

L..-

 

 

 

Рис. 8.25. Шаблон Null Object

Delegator. Класс в этой роли участвует в шаблоне Null Object, делегируя опера­ цию абстрактному классу или интерфейсу. Он осуществляет такое делегирова­

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

инкапсулирует правильное поведение даже в случае отсутствия действий.

В частности, объект в роли De legator не должен выполнять проверку на null

перед обращением к методам объекта, которому он делегирует операцию.

OperationIF. Класс в роли Delegator делегирует операцию интерфейсу, вы­

ступающему в этой роли. Эту роль может выполнять также абстрактный класс.

RealOperation. Классы в этой роли реализуют операцию, которую класс Dele­ gator делегирует интерфейсу Operation I F.

NullOperation. Классы в этой роли обеспечивают бездействующую реализацию операции, которую класс Delegator делегирует интерфейсу OperationIF.

РЕАЛИЗАЦИЯ

Довольно часто экземпляры классов NullOperat ion не содержат информа­

ицию, зависящую от экземпляров. В таком случае можно сэкономить время память, реализуя класс NullOperation как класс-одиночку.

СЛЕДСТВИЯ

© Шаблон Null Object освобождает класс, делегирующий операцию другому классу, от ответственности за реализацию бездействующей версии этой операции. Он упрощает код, который теперь не должен производить про­ верку на nu l l перед вызовом метода, реализующего делегированную опера­ цию. Шаблон Null Object повышает надежность кода, так как он устраняет потенциальную возможность ошибок, которые могут быть связаны с про­ пуском проверки на null в исходном тексте.

© Бездействующее поведение, которое инкапсулировано в классе, выступаю­ щем в роли Nul lOperation, является многократно используемымдЛЯ , если

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

сов Delegator.

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

®Использование шаблона Null Object приводит к увеличению количеСТва

классов в программе. Если не сушествует интерфейса, исполняющего роль Operation I F, то шаблон Null Object, вводя дополнительные классы, значи­ тельно усложняет программу, несмотря на некоторое упрощение кода.

ПРИМЕР КОДА

Приведем код, реализующий классы, представленные в разделе « KOHTeKcTI>. Сначала - интерфейс WarningRouter, реализуемый классами, обеспечиваю­

щими соответствующую среде обработку предупреждающих сообщений.

public interface WarningRouter {

/ * * * Этот метод передает предупреждающее сообщение

*

по лЮбому адресу,

который он считает подходящим .

*

@ return Возвращает

true , если тот, кто его вызвал, должен

*продолжить свою текущую операцию .

* /

public boolean routeWarning (String тзч)

/ / inter face WarningRouter

Далее приведем фрагмент кода класса Bus inessRules, который делегирует об­ работку предупреждающих сообщений объектам, реализующим интерфейс

WarningRouter.

class BusinessRule

 

 

 

private WarningRouter warning;

 

 

private Date expirationDate

=

new Date (Long . М

AX

 

_VALUE) ;

BusinessRule ( ) {

if (new Date () . after (expirationDate»

String msg = getClass ( ) . getName () +" has expired. " ; warning . routeWarning (msg) ;

/ / i f

// const ructor ( )

// class Bus ines sRule

Теперь напишем класс, который реализует интерфейс WarningRouter, пока­

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

class WarningDialog implements WarningRouter { public boolean routeWarning (String warning)

int r;

Null Object - 41

r = JOptionPane . showConfirmDialog(null , warning,

"Warning" ,

JOptionPane . OK_CANCEL_OPTION,

JOptionPane .WARNING_МESSAGE) ; return r == О ;

// routeWa rning (String)

// class WarningDia log

Метод routeWarning класса WarningDialog возврашает true, если пользова­ тель шелкает на кнопку О К диалогового окна, или false, если пользователь шел­ кает на кнопку Cancel. Следуюший листинг - это класс IgnoreWarning. Он

инкапсулирует бездействуюшее поведение, поэтому его метод routeWarning

всегда возврашает true.

class IgnoreWarning implements WarningRouter { public boolean routeWarning (String warning)

}

return true;

// routeWa rning (String)

/ /

class I gnoreWarning

ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ

С ШАБЛОНОМ NULL OBJECT

Strategy. Шаблон Null Object часто используется вместе с шаблоном Strategy.

Singleton. Если экземпляры класса Nul lOperation не содержат информации, зависяшей от экземпляров, то можно сэкономить время и память, реализуя та­ Кой класс NullOperation как класс-одиночку.

Этот шаблон был описан в работе [GoF95].

СИНОПСИС

Связанные алгоритмы инкапсулируются в классах, реализующих общий ин­

терфейс. Это позволяет выбирать алгоритм путем определения соответствую­

щего класса. Кроме того, этот шаблон со временем позволяет изменять выбор алгоритма.

КОНТЕКСТ

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

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

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

одном из конкретных наборов праздников. Такой проект показан на рис. 8.26.

I

I

 

 

Использует

 

 

 

 

 

 

CalendarDisplay J 1

..

 

 

«interface»

 

 

 

HolidaySetIF

 

 

0. .1

getHolidays(:Oate): String[]

 

 

 

 

 

 

 

*

 

 

Ir - - - - - - - - - - - - - - - - - - - - - - - -Ir - - - - - - - - - - -- - - - ,- - I - - - - - - - - - _

,

 

 

I

 

I

I

I

I CanadaHolidaySet

I

I

USHolidaySet

. . .

 

 

 

 

 

 

 

 

 

 

Рис. 8.26. Классы Holiday

1. .*

 

 

_ _ _ о _ о ,I

 

 

I

I

 

 

l CompositeHolidaySetJ

Рассмотрим, как показанные классы работают друг с другом. Если объеКТ

CalendarDi splay использует в своей работе объект Hol idaySetIF, он опра­

шивает этот объект. является ли отображаемый день праздничным. Такие объек­

ты являются либо экземпляром класса типа USHoliday, идентифицируюшего единственный набор праздников, либо экземпляром класса Compos i teHo l iday·

Strategy 417

Класс Compos i teHo 1idaу работает в том случае, когда пользователю нужно отображение нескольких праздничных дат. Инстанциирование производится

посредством передачи массива объектов Holiday его конструктору.

Такая организация позволяет объекту CalendarDisplay выяснять, какие празд­ ники попадают на определенные даты, простым вызовом метода getHol idays

объекта Hol idaySe t I F.

МОТИВЫ

©Программа должна обеспечивать различные варианты алгоритма или поведения.

©Нужно изменять поведение каждого экземпляра класса.

©Необходимо изменять поведение объектов на стадии выполнения.

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

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

РЕШЕНИЕ

На рис. 8.27 представлена диаграмма классов, демонстрирующая классы в шаб­ лоне Strategy.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

I

 

--.l1 I

 

Использует

 

 

 

 

 

 

«interface»

 

Client

1

0

.

.1

 

 

 

StrategyIF

 

L

.....___-

 

 

 

 

operation() *'

 

 

 

 

 

 

- --I

 

- - -

-

 

- - -

- - - - - - - - - - - - - - -I

- -

- - - - - - -II

 

 

 

 

ConcreteStrategy1

 

ConcreteStrategy2

 

 

 

 

 

 

 

Рис. 8.27.

Шаблон Strategy

 

Client. Класс в роли C l i ent делегирует операцию интерфейсу. При этом он Ничего не знает о реальном классе объекта, которому он делегирует операцию,

или о том, каким образом этот класс реализует операцию.

StrategyIF. Интерфейс в этой роли обеспечивает общий способ доступа к опе­ рациям, инкапсулированным в его подклассах.

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

ConcreteStrategyl, ConcreteStrategy2 и Т.Д. Классы в этой роли предоставлКЛасSlIO'rс

альтернативные варианты реализации операции, которую делегирует

Cl ient.

Шаблон Strategy всегда используется вместе с механизмом для определения ре. ального объекта ConcreteStrategy, который будет использоваться объектИЛомI1 Cl ient. Выбор стратегии часто обусловлен информацией о конфигурации событиями. Реальный механизм может меняться весьма значительно, поэтому ни один конкретный механизм выбора стратегии в шаблон не включен.

РЕАЛИЗАЦИЯ

Обычно классы ConcreteStrategy имеют некоторые общие операции. Нужно определить такие операции в общем суперклассе.

Возможны ситуации , когда ни один из вариантов поведения, инкапсулирован­

ных в классах ConcreteStrategy, не является подходящим. Как правило, об·

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

проверки на null перед обращением к методу объекта S trategy. Если струк­ тура объекта Cl ient не позволяет это сделать, можно использовать шаблон

Null Object.

СЛЕДСТВИЯ

©Шаблон Strategy позволяет динамически определять поведение каждого из

©Шаблон Strategy упрощает классы Client, освобождая их от какой-либо ОТ­

ветственности за выбор поведения или за реализацию альтернативных ва­

риантов поведения. Он упрощает код для объектов Client, устраняя опера­ торы i f И swi tch. В некоторых случаях он также увеличивает производи­

тельность объектов C l ient, поскольку им не нужно затрачивать какое-тО

время на выбор поведения.

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

Пакет j аvа . иt i 1 . z ip содержит некоторые классы, которые используют шаб­

лон Strategy. Оба класса - CheckedInputStream И CheckedOutputSt ream ­

используют шаблон Strategy для вычисления контрольных сумм для байтОВЫХ

потоков. Эти два класса исполняют роль классов Cl ient. Конструкторы обоИ классов принимают в качестве аргумента интерфейс Checksum, выступаюшИЙ в роли AbstractStr ategy. Интерфейс Checksum реализуется с помощью дв1-)'111Х классов: Аdl е r З 2 и СRСЗ 2, они исполняют роль ConcreteStrategy.

рис. 8.28 представлена диаграмма, показывающая отношения между всеМ"

этими классами.

ПРИМЕР КОДА

flриведем код, реализующий проект, представленный в разделе «Контекст.). flервый листинг содержит интерфейс Hol i daySetIF, определяющий метод, J(»ОТуюОРЫЙ возвращает массив с названиями праздников, выпадающих на задан­

дату. В шаблоне Strategy он выполняет роль StrategyIF.

public interface HolidaySetIF (

public String [] NO_HOLIDAY = new String [O] ;

/ * *

 

 

*

Возвращает массив строк, описывающих

праздники, которые

* выпадают на определенную дату .

 

*

Если на эту дату не выпадает никаких

праздников,

*возвращает NO_HOLIDAY .

*/

public String [] getHolidays (Date dt)

// interface Hol idaySetIF

 

Использует

 

«interface»

IE----...Ис ользу.:..ет-j....-

--

CheckedlnputStream

 

 

 

п

CheckedOutputStream

 

 

 

Checksum

 

 

 

L...---------I l1

"'-

.........1

 

1 L..._____- ---I

 

-:-

 

 

 

 

__ __

 

 

 

 

 

 

I

 

 

 

 

 

 

 

II

 

 

 

 

 

- - - - -- - - - -- - - - - - - - - - - - - - .

 

 

 

 

I

 

 

I

 

 

 

 

I

 

 

I

 

 

 

 

I

I

 

I

 

 

 

 

Дdl rЗ2

 

СRСЗ2

 

 

 

Рис. 8.28.

Классы, связанные с Checksum

 

 

в интерфейсе Hol i daySet I F объявлен массив NO_HOLI DAY нулевой ДЛИНЫ,

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

избежать создания аналогичных массивов в конкретных реализациях интер­ Фейса Hol idaySet I F для дней, не являющихся праздничными.

далее следует фрагмент листинга для класса CalendarDi spl ay, который уча­

Ствует в шаблоне Strategy в качестве класса Client.

class CalendarDisplay {

private HolidaySetIF holldaY i

/ * * * Экземпляры этого закрытого класса используются

* для кэширования информации,

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