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

GrandM-Patterns_in_Java

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

Composite 1 97

интерфейс, ВЫC1)'I1ающий в роли ComponentIF, объявляет специальные мето­

ды в том случае, если они нужны какому-либо классу ConcreteComponent.

Принцип объектно-ориентированного проектирования заключается в том,

что специальные методы должны находиться только в тех классах, которым они нужны. Как правило, класс должен иметь методы, обеспечивающие связанную функциональность и образующие сцепленный набор. В этом принципе заключена суть шаблона Low CouplingfHigh Cohesion, описанно­ го в книге [Gгапd99]. Помещение специального метода в класс общего на­

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

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

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

ключение из широко распространенного правила основано скорее на прак­ тике, чем на теории.

@) Шаблон Composite позволяет любому объекту Component IF быть потомком AbstractCompos i te. Если нужно задать более строгое отношение, необхо­ димо добавить в класс AbstractComposite или его подклассы код, кото­ рый будет осведомлен о типе объекта. При этом несколько снижается цен­ ность шаблона Composite.

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

впакете j ava . awt содержится удачный пример шаблона Composite. Его класс

Component играет роль ComponentIF, а класс Container выполняет роль

AbstractCompos i te. У него есть классы, выступающие в роли Concre­ tecomponent, включая Label, TextField и Button. Роль ConcreteComposite

играют классы Pane l, Frame и Dialog.

ПРИМЕР КОДА

вПРименение шаблона Composite рассмотрим на примере задачи, опис нной

разделе «Контекст». На рис. 6.8 представлена подробная диаграмма классов.

На рис. 6.8 изображены некоторые методы, не указанные на рис. 6.6. Из сле­

ДУющего кода станет понятно, что метод setFont может служить примером

Взаимодействия с объектом родителя. Метод getCharLength собирает инфор­

I.!ацию у потомков объекта и сохраняет ее в кэше для дальнейшего использова­

Iliойl1я. Метод changeNotification ИСllOльзуется для синхронизации сохранен­

в кэше информации.

Абстрактный класс AbstractDocumentElement содержит общую логику упра-

8.rr ия шрифтами и родительским объектом.

ен

198 Глава 6. Разделяющие шаблоны проектирования

«interface» DocumentElementIF

getFont(). . . : Font * setFont(font:Font)

getCharLength() : int

getParentO : CompositeDocumentElement

I

AbstractDocumentElement

getFontO.. . : Font setFont(font:Font)

getParentO : CompositeDocumentElement setParent(parent:CompositeDocumentElement)

 

 

 

 

 

 

 

 

 

О

 

I

 

 

 

 

 

I

Character

 

 

Image

 

 

CompositeDocumentE/ement

getCharLengthO. . .

: int

 

getCharLength(). . .

: int

 

getChild(index:int) : DocumentElement

 

 

 

 

 

 

 

addChild(child:DocumentElement)

 

 

 

 

 

 

 

removeChild(child:DocumentELement)

 

 

 

 

 

 

 

changeNotification(). . .

 

 

 

 

 

 

 

 

getCharLengthO : int

 

 

I

 

I

 

 

I

 

 

I

f

 

 

I

 

Document

I

Page

I

I

CoLumn

I

I

Frame

I

 

I

LineOfТext

 

 

 

Рис. 6.8.

Детализированная диаграмма классов

 

 

 

I

I

Рассмотрим код для интерфейса DocumentElementIF, который должен реалИ­ зовываться всеми классами, образующими документ:

public interface DocumentElementIF (

/**

*

* Возвращает родителя этого объекта и л и nu l l , если родитель */отсутствует .

public CompositeDocumentElement getParent ()

/**

Composite • 1 уу

 

*/

Возвращает шрифт , связанный с данным объектом .

*

 

public Font getFont ()

/ *

*

 

*

 

Связывает шрифт с зтим объектом .

*/

@param font Шрифт, связываемый с зтим объектом .

*

 

public void setFont (Font font)

/ * *

 

*/

Возвращает количество символов , содержащихся в этом объекте .

*

 

public int getCharLength ()

//

interface DocumentElemen t I F

Теперь листинг класса AbstractDocumentElement, который содержит общую логику по управлению шрифтами и родительским объектом:

aЬstract class AbstractDocumentElement

/**

implements DocumentElementIF

 

 

*

 

Это шрифт , связанный с данным объектом .

*

 

Если эта переменная - null,

*

/

то шрифт этого объекта наследуется от предка .

*

 

private Font font;

/**

 

*/

Контейнер этого объекта .

*

 

private CompositeDocumentElement parent;

/**

 

*

 

Возвращает родителя этvго объекта и л и nul l , если у него нет

*/

родителя .

*

 

public CompositeDocumentElement qetParent () {

00 Глава 6. Разделяющие шаблоны проектирования

return parenti } // getParent ( )

/* * * Задает родителя этого объе та .

*/

protected void setParent (CompositeDocumentElement parent) {

}

this .parent = parenti

// setParen t (Abs tractDocumentElement)

/ * *

 

*

Возвращает Font,

связанньrn с данным объе том .

*

Если нет шрифта ,

связанного с данным объе том,

*

то возвращает шрифт, связанный с родителем этого объе та .

*

Если нет шрифта ,

связанного с родителем данного объе та , то

* возвращает nul l .

 

* /

 

public Font getFont ( )

 

i f (font ! = null)

 

return fonti

! = null)

 

else if (parent

 

return parent . getFont ( ) i

 

else

 

}

return nulli

 

/ / ge tFont ( )

 

/**

 

*

Связывает Font с этим объе том .

*

@param font

 

*

Шрифт , oTopьrn связывается с этим объе том .

* /

public void setFont (Font font) ( this . font = fonti

// setFont ( Font ) .

/ * *

*

Возвращает

оличество символов ,

содержащихся

в

этом

*

объе те .

 

 

 

 

*/

 

 

 

 

public abstract int getCharLength ( ) i } // class AbstractDocumentElement

Composite 201

код для класса Compos iteDocumentElement, который представляет собой аб­

страктный суперкласс ДЛЯ всех элементов документа, содержащих другие эле­

менты документа:

public abstract class CompositeDocumentElement

extends AbstractDocumentElement {

//

Коллекция потомков этого объекта .

private Vector children

= new Vector ( ) ;

/**

 

 

*

Значение , помещенное

в кэш, после предыдущего выэова

*

метода gеtСhаrLепgth , или -1, если charLength

 

*

не содержит помещенного в кэш значения .

* /

 

 

private int cachedCharLength = -1 ;

/**

*Возвращает объект потомка этого объекта ,

*находящегося в данной позиции .

*@pa ram index

*Индекс потомка .

* / public DocumentElementIF getChild(int index) {

return (DocumentElementIF) children . elementAt (index) ; } // getChild ( in t )

/**

*Делает данный DocumentElementIF потомком этого объекта .

*/

public

synchronized void addChild (DocumentElementIF child) { synchronized (child) {

children . addElement (child) ;

« AbstractDocumentElement) child) . setParent (this) ; changeNotification () ;

// synchronized

/ / addChil d ( DocumentEleme/lt IF)

Оба метода addChild и removeChild являются синхронизированными и, кроме

Того, содержат синхронизирующий блок ДЛЯ блокировки данного потомка. Это

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

202 Глава 6. Разделяющие шаблоны проектирования

1 * *

*

Сделаем Ta , чтобы DocumentElementIF НЕ был потом ом

*

данного объе та .

*1

public synchronized

void removeChild(AbstractDocumentElement child) { synchronized (child) {

if (this == child.getParent ( » child. setParent (null)

11 if

children . removeElement (child) ; chanqeNotification () ;

} /1 synchronized

11 removeChild (AbstractDocumentElement)

/ * *

 

 

 

 

 

 

*

Вызов

этого метода означает, что один из потом ов

 

*

был изменен та им образом, что он

 

 

*

делает

недействительными все данные о потом ах,

OTopыe

 

*

этот объе т может помещать в эш .

 

 

* 1

 

 

 

 

 

public void chanqeNotification ( )

 

 

 

cachedCharLenqth

=

-1;

 

 

 

if

(qetParent ( )

! =

null)

 

 

 

qetParent ( ) . changeNotification ( ) ;

 

} /1 changeNoti fication ( )

 

/

*

*

 

 

 

 

 

 

Возвращает оличество символов, содержащихся в

этом

 

* объе те .

 

 

 

 

*1

 

 

 

 

 

 

 

int len = О ;

 

 

 

public

int qetCharLenqth ()

 

 

 

for

(int i = О ;

i <

children . size () ; i++)

 

 

 

AbstractDocumentElement thisChild;

 

 

 

thisChild

 

 

 

 

 

 

=

(AbstractDocumentElement) children . elementAt (i) ;

 

 

len

+= thisChild . getCharLength ( ) ;

 

Composite 8 203

} 11 for

= len;

cachedCharLength

return len;

 

}11 getCharLength ( )

1 1 class Compos i teDocumentElement

((ласс Image может служить примером класса, реализующего некоторый метод, J(ОТОРЫЙ позволяет другим классам, образующим документ, не знать о классе тодImage то, что он может нуждаться в некоторой специальной обработке. Его ме­ getCharLength всегда возвращает 1, поэтому изображение может рассмат­

риваться просто как большой символ.

class Image extends AЬstract DocumentElement

public int getCharLength () return 1 ;

} / 1 getCharLength ( )

1 / class Image

Другие классы, представленные на диаграмме классов и являюшиеся подклас­ сами класса Compos iteDocumentElement, не содержат каких-либо свойств, которые могут быть интересны при рассмотрении шаблона Composite. Для краткости просто приведем один класс, который выглядит примерно так:

class Раче extends CompositeDocumentElement (

} 1 / class Page

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

С ШАБЛОНОМ COMPOSITE

Chаiп оС RеsропsiЬШty. Шаблон Chain of Responsibility может использоваться ВМесте с шаблоном Composite посредством добавления звеньев «потомок - ро­

IIИтель» таким образом, что потомки могут получать информацию от предка, не

ЗНая, от какого предка она получена.

w СоuрliпgjНigh Соhеsiоп. Шаблон Low CouplingjНigh Cohesion (описанный В l<ниге [Grand99]) не рекомендует помещать специальные методы в классы об­ lI.{ero назначения, что иногда делает шаблон Composite.

sitor. Можно использовать шаблон Visitor для инкапсуляции в одном классе

Dnераций, которые в противном случае были бы распределены по множеству

Впервые шаблон Read-Only Interface был описан в работе [LLOI).

СИНОПСИС

Необходимо, чтобы объект изменялся только некоторыми его клиентами. Шаблон Read-Only Interface гарантирует, что клиенты, которым не позволено тупизменять объект, не будут его изменять блаroдаря тому, что они получают дос­ к этому объекту через интерфейс, который не содержит каких-либо мето­

дов, способных изменить объект.

КОНТЕКСТ

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

 

 

 

 

1.1.1: deLiverEvent

 

 

 

 

 

 

:TemperatureListener

 

:TemperatureData

 

deLiverTemperature

i

 

 

 

 

 

 

 

 

 

1: deLiverEvent

 

 

1.1:

 

 

)

 

 

 

 

 

 

:SensorIF

 

 

2: deLiverEvent

 

 

 

 

 

 

 

 

 

 

:SensorControLLer

 

!

 

 

 

 

 

 

 

 

 

 

 

2.1.1: deLiverEvent

 

 

2.1: deLiverDoorStatus

 

:DoorListener

 

 

 

 

 

 

 

 

 

 

 

 

jDoorData

 

 

 

 

 

 

 

 

 

 

Рис. 6.9. Взаимодействие контроллера безопасности

Оба взаимодействия, представленные на данном рисунке, инициируют сенсоР,

программной реализацией которого является интерфейс SensorIF. В ходе пер­

вого взаимодействия температурный сенсор передает значение температурЫ

и TemperatureData,

Read-Only Interface 205

объекту SensorController контролирующей программ ы . Во втором взаимо­ действии сенсор передает изменение состояния двери (открыта, закрыта, забло­ кирована и т.д.). Все сенсоры передают данные объекту SensorControl ler. Объект SensorControl ler отвечает з а идентификаци ю и определение типа сенсора, отправивщего данные. Он передает данные тому объекту контроли ­ рующей програ м м ы , которы й соответствует сенсору, отправивщему данные.

Каждый uбъект данных, например, объекты DoorData

представленные на р и с . 6.9, соответствуют определенному сенсору. Каждый содержит данные, отражающие самую последнюю информаци ю , полученную от соответствующего сенсора. Когда объект данных получает новые данные, он

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

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

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

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

чать доступ к объектам данных через интерфейс, который не содержит методов,

изменяющищая х содержимое объекта данных. На рис. 6. 1О показана соответствую­ часть такой организаци и объектов.

l

l

«interface»

 

 

L

(читывает значение температуры

TemperatureListener " ---------------1-'1

 

 

 

 

 

 

 

«interface»

 

 

 

 

 

 

 

 

 

TemperatureIF

 

 

 

 

 

 

 

 

 

getТemperatureO:double

 

 

 

 

 

 

 

 

 

addTemperatureListener(:TemperatureListener)

 

 

 

 

 

 

 

 

 

removeTemperatureListener(:TemperatureListener)

 

 

 

 

 

 

 

 

 

t:I.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

--I'-

 

 

 

 

 

Записывает значение

 

 

TemperatureData

 

 

 

 

 

 

 

getTemperatureO:double

 

 

 

I

 

температуры

 

 

 

SensorController

1

setTemperature(temp:double)

 

I

 

 

addTemperatureListener(:TemperatureListener)

 

 

 

 

Рис. 6.10.

 

 

removeTemperatureListener(:TemperatureListener)

 

 

 

 

 

 

Интерфейс данных

206 Глава 6. Разделяющие шабланы проектиравания

Класс SensorController прямым образом использует класс TemperatureData. Он имеет возможность задавать значение температуры в объектах Tempera­ tureData, вызывая их метод setTemperature. Объекты, реализующие интер­ фейс ТеmреrаturеListелеr, могут получать значения температуры от объекта TemperatureData, поскольку в интерфейсе TemperatureIF объявлен метод getTemperature. Однако объекты ТеmреrаturеLis tелеr не могут изменять значение температуры, хранящееся в объекте TemperatureData, поскольку они получают доступ к объекту TemperatureData через интерфейс Tempera­ tureI F, в котором не объявлен метод setTemperature.

моти в ы

©Существует класс, экземпляры которого одни классы могут изменять, а дру­ гие - нет.

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

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

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

рованию.

РЕШЕНИЕ

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

ектов осуществлять доступ к изменяемому объекту через интерфейс, который содержит каких-либо методов, изменяющих этот объект (рис. 6. 1 1).

Опишем роли, исполняемые классами и интерфейсом в шаблоне Read-Only

Interface.

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

ReadOnlyIF. Интерфейс, играющий эту роль, имеет такие же методы get, чТО

И класс Mutable, реализующий этот интерфейс. Однако этот интерфейс не со­

держит каких-либо методов, которые MOгyr заставить объект Mutable изменИТЬ

свое содержимое.

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