GrandM-Patterns_in_Java
.pdfComposite • 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 ия шрифтами и родительским объектом.
ен
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
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ераций, которые в противном случае были бы распределены по множеству
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 изменИТЬ
свое содержимое.