GrandM-Patterns_in_Java
.pdf146 • Глава 5. Порождающие шаблоны проектирования
PrototypeI F как расширение интерфейса C loneable, можно будет сэконо мить время. Таким образом, все классы, реализующие интерфейс PrototypeI F,
будут реализовывать также интерфейс Cloneable.
Некоторые объекты, например потоки и сокеты, не могуг просто копироваться или совместно использоваться. Какая бы стратегия копирования ни применя
лась, если имеются ссылки на такие объекты, то для использования скопиро
ванных объектов придется создавать эквивалентные объекты.
Если в палитре объекта C li ent, состоящей из объектов-прототипов, количест
во объектов непостоянно, то неудобно использовать отдельные переменные
для ссылки на каждый объект-прототип. Проще использовать объект коллек
ции, который может содержать динамически расширяющуюся или сужающую
ся палитру, состоящую из объектов-прототипов. Объект коллекции, исполняю
ший эту роль в шаблоне Prototype, называется управляющим прототипом.
Управляющие прототипами могуг быть сложнее, чем простая коллекция. Они могуг позволять получать объекты из палитры при помоши значений их атри бутов или других ключей.
Если программа имеет множество клиентских объектов, необходимо рассмот реть другой вопрос. Будут ли клиентские объекты иметь свои собственные па литры, состояшие из объектов-прототипов, или они будут совместно использо вать одну и ту же палитру? Ответ на этот вопрос зависит от требований приложения.
СЛЕДСТВИЯ
©На стадии выполнения программа может динамически добавлять и удалять объекты-прототипы. Это значительное преимущество, которое не может
предложить ни один другой порождаюший шаблон проектирования, опи санный в данной книге.
©Объект PrototypeBui lder может просто предоставлять постоянный набор
объектов-прототипов.
©Объект PrototypeBui lder может обеспечивать дополнительную гибкость,
допуская создание новых объектов-прототипов посредством комбинации
объектов или изменения значений атрибутов объектов.
©Клиентский объект тоже можетсоздавать объекты-прототипы новых видов.
В примере программы рисования, рассмотренном ранее, клиентский объ
ект мог бы вполне разумно позволить пользователю нарисовать свой значоК
и затем вставлять его в свою палитру.
©Клиентский класс не зависит от конкретного класса объектов-прототипов, который он использует. Кроме того, клиентскому классу не нужно знать
подробности создания объектов-прототипов.
© Объекты PrototypeBui lder инкапсулируют детали построения объек
тов-прототипов.
Pгototype • 147
©Если объекты-прототипы будут реализовывать интерфейс, например, Pro totype I F, то шаблон Prototype гарантирует предоставление объектами-про тотипами согласованного набора методов, используемого клиентскими
• объектами.
Нет необходимости в том, чтобы объекты-прототипы были представлены в виде какой-либо иерархии классов.
® Недостатком шаблона Prototype является дополнительное время, затрачи
ваемое на написание классов PrototypeBui lder.
® Программы, использующие шаблон Prototype, зависят от динамической
компоновки или аналогичных механизмов. Инсталляция таких программ может быть более сложной.
ПРИМЕНЕНИЕ В JAVA APIдля
Шаблон Prototype очень важен JavaBeans. JavaBeans - это экземпляры классов, которые удовлетворяют определенным соглашениям об именах. Со глашения об именах позволяют программе создания компонентов (Beans) знать, как их настраивать. После настройки объекта компонента с целью ис пользования его в приложении, объект сохраняется в файле, который загружа ется приложением на стадии выполнения. Этот способ клонирования объектов требует дополнительного времени.
ПРИМЕР КОДА
Предположим, что нужно написать интерактивную ролевую игру, которая по зволит пользователю взаимодействовать с персонажами, которые моделируют ся компьютером. Может быть, игрокам со временем надоест общаться с одни
ми и теми же действующими лицами, и они захотят поиграть с новыми
героями. Поэтому необходимо разработать также расширение игры, которое содержит несколько предварительно созданных действующих лиц и программу
для создания дополнительных героев.
Используемые в игре действующие лица - это экземпляры относительно не
большого количества классов, например, Hero, Foo l , Vi l l ian и Monster.
Различие между всеми этими экземплярами одного и того же класса заключает
ся в различии задаваемых для них значений атрибутов, например, изображе
ний, которые используются для отображения на экране их роста, веса, ума и ловкости.
На рис. 5. 1 5 показаны некоторые классы, используемые в игре.
етНиже представлен код для интерфейса Character I F. Этот интерфейс выступа
в роли PrototypeI F.
public inter ace CharacterIF extends cloneaыle { public String getName () ;
148 • |
Глава 5. Порождающие шаблоны проектирования |
|||
|
|
; |
|
|
|
public void setNaтe (String пате ) |
; |
|
|
|
public Image getImage ( ) |
|
|
|
|
|
|
|
|
|
public void setImage (Image image) |
|
|
|
|
public int getStrength () ; |
|
|
|
|
public void setStrength (int strength) |
|||
} |
// class CharacterI F |
|
|
|
|
|
|
|
|
|
|
|
|
((interface» |
|
|
|
|
CLoneable |
|
|
|
|
|
|
getRandomCharacter |
|
....... |
|
|
||
|
|
|
|
||||
|
CharacterManager |
|
Использует |
|
|||
|
addCharacter... |
|
|
|
|||
|
|
|
|
1 |
|
1..• |
|
|
регистратор |
1 |
|
|
|
|
|
|
|
|
|
||||
|
|
Создает и регистрирует |
|
||||
|
|
|
|||||
|
|
объекты |
|
|
|
|
|
|
создатель |
0..* |
|
|
|
|
|
|
CharacterLoader |
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
Hero |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getBravery |
|
|
|
|
|
|
|
setBravery |
|
|
|
|
|
|
|
|
«interfoce» ChorocterIF
getImage.. setImage
. I
I
I
Chorocter
getImage.. setImage
.
f
Monster
getViciousness setViciousness
Рис. 5.15. Использование шаблона Prototype
А теперь приведем исходный текст ДIlЯ класса Character - абстрактного клас
са, выступающего в роли Prototype:
public abstract class Character implements CharacterIF {
/**Замещаем clone - делаем его открытым .
*
*/
public Object clone () { try (
return super . clone () ;
Pгototype • 149
catch (CloneNotSupportedException е) { // Этого не должно случиться никогда,
// так как этот класс реализует Cloneable . throwtry new InternalError () ;
//
/ / clone ( )
public Strinq qetNaтe ( ) |
{ return пате ; } |
|
|||
public |
void |
setNaтe (Strinq пате) { |
this . name |
пате ; } |
|
public |
Imaqe |
qetImaqe ( ) |
{ return imaqe ; } |
= imaqe ; |
|
public void |
set Imaqe (Imaqe imaqe) |
{ this . imaqe |
}// class Character
Здесь в основном используются простые методы доступа. Один менее очевид ный - метод clone. Все объекты наследуют метод клонирования от класса Obj ect. Поскольку этот метод не является открытым в классе Obj ect, класс Character должен замещать его, объявляя открытым и тем самым делая его доступным для других классов.
Приведем исходный текст программы для класса Hero. Это один из классов,
который выступает в роли прототипа:
public class Hero extends Character private int bravery;
public int qetBravery ( ) { return bravery;
public void setBravery= (int bravery) { this . bravery bravery;
}
// class Hero
Класс Monster похож на класс Hero.
Приведем код для класса CharacterManager, играющего роль клиентского l<Ласса:
public class CharacterМanaqer= {
private Vector characters new Vector () ;
150 • Глава 5. Порождающие шаблоны проектирования
/** * Возвращаем копию случайно выбранного объекта
* персонажа из коллекции .
* /
Character= getRandomCharacter ()
int i (int) (characters . size () *Мath . random(» ; Character с = (Character) characters . elementAt (i) ; return (Character) c . clone () ;
// getRandomCharacter ( )
/**
*Добавляем объект-прототип в коллекцию .
*/
void addcharacter (Character character) characters . addElement (character) ;
}// addCharacter ( Character)
}// class CharacterManager
Приведем коддля класса CharacterLoader, играющего роль PrototypeBuil der:
/**
*Этот класс загружает объекты действующих лиц
*и добавляет их в CharacterManager .
*/
class CharacterLoader (
private CharacterКanager mgr;
* CharacterManager, с KOTOPЬ будет работать этот объект .
* /
= ст)
CharacterLoader (CharacterМanager mgr ст;
} // Constructor (CharacterManager)
/ * *
* |
Загружаем объекты действующих лиц из заданного файла . |
|
* |
Так как сбой при загрузке влияет только на остальную часть |
|
* |
про граммы, |
не позволяя добавить в игру новые объекты |
* |
персонажей, |
нам не нужно генерировать какие-либо |
* |
исключения . |
|
*/ |
|
|
Prototype • 151
int loadCharacters (String= fname) {
int objectCount О ; // Количество загруженных объектов .
// Если создание InputStream заканчивается неудачно ,
//просто выходим из метода .
try
Inputstream in; |
|||
in |
= |
new FileInputStream(fname) ; |
|
in |
= |
new BufferedInputStream (in) ; |
|
ObjectInputStream oIn = new ObjectInputStream(in) ; |
|||
while (true) |
{ |
||
|
Object с |
= oIn . readObject () ; |
|
|
if |
(с instanceof Character) { |
|
|
|
mgr . addCharacter « Character) c) ; |
|
|
} |
/ / i f |
|
}/ / while
}catch (Exception е) { // try
return objectCount;
//loadCha racters ( String)
}// class Characte rLoader
ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ
с ШАБЛОНОМ PROTOТYPE
Composite. Шаблон Prototype часто используется вместе с шаблоном Composite.
Композиция применяется для организации объектов-прототипов.
Abstract Factory. Шаблон Abstract Factory может быть хорошей альтернативой шаблону Prototype в тех случаях, когда не нужны допускаемые шаблоном
Prototype динамические изменения палитры объектов-прототипов.
Классы PrototypeBui lder могут использовать шаблон Abstract Factory для
создания набора объектов-прототипов.
Facade. Клиентский класс обычно действует как класс-фасад, отделяющий
другие классы,участвующие в шаблоне Prototype, от остальной части проrpаммы.
Factory Method. Шаблон Factory Method может быть альтернативой шаблону
Prototype в том случае, если палитра, состоящая из объектов-прототипов, все
ГДа содержит не более одного объекта.
Decorator. Шаблон Prototype часто используется вместе с шаблоном Decorator
дЛя составления объектов-прототипов.
Этот шаблон был ранее описан в работе [GoF95].
СИНОПСИС
гоШаблон Singleton гарантирует, что создается только один экземпляр некоторо класса. Все объекты, использующие экземпляр этого класса, имеют дел
с одним и тем же экземпляром.
КОНТЕКСТ
Некоторые классы должны иметь только один экземпляр. Эти классы обычн имеют дело с централизованным управлением каким-то ресурсом. Ресурс мо жет быть внешним (как в случае с объектом, управляющим многократным ие пользованием соединений баз данных) и внyrpенним (например, объект, с держащий счетчик ошибок и другие статистические данные, предназначенн для компилятора).
Предположим, что нужно написать класс, который может использоваться ап плетом с целью воспроизведения только одного аудиоклипа в какой-то моме Если апплет содержит два фрагмента кода, которые независимо друг от др могут воспроизводить аудиоклипы, то существует вероятность, что оба клип будут воспроизводиться одновременно. В итоге может получиться что угодн ,
начиная от ситуации, когда пользователи слышат оба аудиоклипа вместе, канчивая тем, что звуковоспроизводящий механизм платформы не может сп виться с одновременным воспроизведением двух аудиоклипов.
Чтобы предотвратить нежелательную ситуацию проигрывания двух аудиокл пов в одно И то же время, создаваемый класс должен прервать проигрывани аудиоклипа до того, как начнет воспроизводиться следующий. Способ проекз,!" тирования класса для осуществления такой политики, в то же время сохраня этот класс простым, заключается в том, что должно гарантироваться наличиобъ": только одного экземпляра такого класса, совместно используемого всеми ектами, применяющими этот класс. Если все запросы на проигрывание ауди - клипов идут через один и тот же объект, то этот объект может просто npepвaTI!t какой-то аудиоклип перед началом воспроизведения следующего. На рис. 5. 1' представлен такой класс.
Конструктор класса AudioCl ipManager является закрытым. Это запрещает другому классу прямым образом создавать экземпляр класса AudioCl ip!vlanager. Вместо этого для получения экземпляра класса AudioCl ipManager другие классы должны вызывать его метод g e t l n s tance. Это статический метод,
который всегда возвращает один и тот же экземпляр класса AudioCl ipManager.
Singleton • 153
AudioCLipManager
-instаnсе:АudiоCliI!Маnаgеr -рrеvCLiр:АudiоCliр
«constructor» ) -АudiоCliрМаnаgеr( «misc»
+getInstance( ):AudioCliI!Manager +pLay(:AudioCliр) +loop(:AudioClip)
+stop( )
. . .
Рис. 5.16. Класс, управляющий аудиокnилом
Возвращаемый им экземпляр - это тот экземпляр, на который ссылается его закрытая статическая переменная instance.
Остальные методы класса AudioClipManager отвечают за контроль над вос произведением аудиоклипов. Класс AudioClipManager имеет закрытую по стояннуюв переменную экземпляра prevClip, которая вначале установлена null, а позднее указывает на последний проигрывавшийся аудиоклип. Перед воспроизведением нового аудиоклипа экземпляр класса AudioClipManager останавливает аудиоклип, на который ссьmается prevClip. Это гарантирует, что предыдущий затребованный аудиоклип останавливается перед началом
проигрывания следующего.
МОТИВЫ
©должен существовать по крайней мере один экземпляр некоторого класса. Даже если методы класса не используют данных экземпляра или использу ют только статические данные, может понадобиться экземпляр такого клас са по разным причинам. Некоторые самые общие причины могут заклю чаться в том, что нужен экземпляр для передачи параметра методу другого класса или необходимо иметь косвенный доступ к классу, через интерфейс.
©Не должно быть более одного экземпляра класса. Это может объясняться тем, что нужно иметь только один источник некоторой информации. На пример, чтобы был единственный объект, который отвечает за генерирова
ние последовательности порядковых номеров.
©Один экземпляр класса должен быть доступен для всех клиентов этого
класса.
® Создание объекта не требует больших затрат, но он занимает большой объ ем памяти или непрерывно на протяжении всего своего времени жизни ис пользует другие ресурсы.
154 • Глава 5. Порождающие шаблоны nроектирования
РЕШЕНИЕ
Шаблон Singleton достаточно прост, поскольку он содержит только один класс
(рис. 5. 17).
SingLeton -siпgLеtопIпstапсе
.. .
«constructol'» -SiпgLеtоп( ) «misc))
+getlnstance( )
.. .
Рис. 5.17. Класс-одиночка
Класс-одиночка имеет статическую переменную, ссылающуюся на такой экзем пляр класса, который нужно использовать. Этот экземпляр создается при за грузке класса в память. Необходимо реализовать класс таким образом, чтобы запретить другим классам создавать любые дополнительные экземпляры клас са-одиночки. Это значит, что необходимо сделать все конструкторы класса за крытыми.
Для доступа к одному экземпляру класса-одиночки класс предоставляет стати ческий метод, обычно с именем getInstance или getClassname, который
возвращает ссылку на единственный экземпляр класса.
РЕАЛИЗАЦИЯ
Хотя шаблон Singleton предполагает относительное простое решение, его реа
лизация включает очень большое количество тонких моментов.
З а к р ыт ы й ко н стру кт о р
Чтобы подчеркнуть природу класса-одиночки, необходимо написать код для
этого класса таким образом, чтобы запретить другим классам прямым образом
создавать экземпляры этого класса. Поэтому все конструкторы класса должнЫ
быть объявлены закрытыми. В данном случае обязательно нужно объявить хотя
бы один закрытый конструктор. Если класс вообще не объявляет никаких кон
структоров, то Java автоматически создаст для него открытый конструктор по
умолчанию.
«Л е н и в о е )) и н ста н ц и и ро в а н и е
Широко распространенный вариант шаблона S ingleton используется в ситуа циях, при которых экземпляр класса-одиночки может не понадобиться. Неиз",,: