GrandM-Patterns_in_Java
.pdf156 • Глава 5. Порождающие шаблоны проектирования
Сериализация может быть использована для копирования объектов, но, как правило, копирование не подразумевает применение сериализации. Сериали зация предназначена для использования в двух следующих общих случаях.
1 . Сериализация применяется для того, чтобы сделать объекты постоян ными. Это достигается посредством преобразования объекта в поток бай тов и записи в файл и позволяет в будущем восстановить объекты. Именно такой механизм используется при сохранении и восстановле нии Java Beans.
2.Сериализация используется для поддержки удаленного вызова проце
дуры, использующей RMI (Remote Method Invocation, удаленный вызов метода). RMI используется EJB (Enterprise Java дляВеаns, серверные ком поненты Java), и RMI использует сериализацию передачи значений
аргументов, а таюке для передачи возвращаемого значения назад вы звавшей стороне.
Обычно эти функции и не выполняются над объектом-одиночкой. В целом объекты Singleton не должны участвовать в процессе сериализации. Этиого можно добиться, если никогда прямо не сериализировать объект-одиночку не иметь никаких классов, хранящих ссылку на объект-одиночку в своих перемен ных экземпляров. Вместо этого следует заставить клиентов класса-одиночки вызывать методы getlnstance этого класса каждый раз, когда они хотят полу чить доступ к объекту-одиночке.
Ко н ку р е н т н ы е о б р а ще н и я к методу getl n sta n c e
Если существует хоть какая-нибудь вероятность того, что многочисленные по токи могут вызывать метод getlnstance класса-одиночки одновременно, то
необходимо убедиться в том, что метод getlnstance не создает многочислен
ных экземпляров класса-одиночки. Рассмотрим следующий код:
public class Foo { private Foo myInstancei
public static Foo getInstance () if (myInstance==null)= {
myInstance new Foo ( ) i } // i f
return myInstance i
//ge t l ns tance ( )
//class Foo
Если два потока вызывают метод getlnstance В одно и то же время и не было никаких предыдущих обращений к этомуметоду, то оба вызова увидят, что зна чение mylns tance равно null, и оба вызова создадут экземпляр класса Foo..;
Singleton • 1 57
Чтобы предотвратить возникновение этой проблемы, можно объявить метод getlnstance как синхронизированный. Тогда в любой момент времени только один поток имеет возможность выполнить метод getlnstance.
При объявлении метода getlnstance синхронизированным увеличивается время его выполнения, поскольку при каждом обращении к этому методу перед
его выполнением должна выполняться блокировка.
Н е я в н а я о ш и б ка
Существует вероятность появления трудно обнаруживаемой ошибки в разных вариантах реализации шаблона Singleton. Эта ошибка может заставить класс-одиночку создавать и инициализировать несколько своих экземпляров. Проблема возникает в программах, которые ссылаются на класс-одиночку только через другие динамически загружаемые классы, описанные в шаблоне
Dynamic Linkage (см. гл. 7).
Некоторые программы организованы так, что они динамически загружают на бор классов, используют эти классы в течение какого-то времени, а затем пре кращают их использовать. Апnлеты, сервлеты и мидлеты· управляются именно таким образом. Если программа перестает использовать классы, они могут быть удалены сборщиком мусора. Все это замечательно. Если программа не иподдерживает ссылки на классы после того, как она перестала с ними работать,
для классов разрешена сборка мусора, то неиспользуемые классы будут в ко нечном счете удалены сборщиком мусора.
Такое поведение может представлять проблему для класса-одиночки. Когда класс-одиночка удаляется при сборке мусора, он будет загружаться снова, если существует другая динамическая ссылка на него. После загрузки класса во вто рой раз очередной запрос на получение его экземпляра будет возвращать новый
экземпляр. Это может привести к неожиданным результатам.
Предположим, есть класс-одиночка, в задачу которого входит поддержка ста
тистических данных, касающихся рабочих показателеЙ. Посмотрим, что слу
Чится, если такой класс будет удален при сборке мусора и загружен повторно.
Первый раз его метод getlnstance вызывается после того, как класс был за
дутгр жен второй раз, и он создает новый объект. Его переменные экземпляра бу
иметь свои начальные значения, и ранее собранные статистические данные будут утеряны.
Если класс загружается объектом ClassLoader, то он не будет удален сборщи
ком мусора до тех пор, пока для объекта ClassLoader не будет разрешена
сборка мусора. Если нельзя на практике осуществить такое управление време
Нем жизни объекта-одиночки, то существует более общий способ. Он заключа |
||
ется в том, чтобы обеспечить существование ссылки, |
ПРЯМОГI |
или косвенной, от |
|
Мидлеты - от англ. M JDlets (MID - Mobile Infonnation Devices, моБилыlеe инфор
мационные устройства). (ПРUlo1еч. ред.)
ующег |
п |
т |
ка на |
бъект, к т рый не |
||
в |
о |
|
о о |
|
о |
о о |
Приведенный здесь класс может быть
д лжен удаляться сб |
рщик |
м |
|
о |
о |
о |
|
использован только для этого:
му
public class ObjectPreserver implements Runnable {
//Это защищает данный класс и все, на что он ссылается ,
//от удаления при сборке мусора .
private static ObjectPreserver lifeLine = new ObjectPreserver () ;
// Этот класс не должен удаляться при сборке мусора, поэтому // не будет удален ни этот HashSet ,
// н и объект , н а который о н ссылается= .
private static HashSet protectedSet new HashSet ( ) ;
private ObjectPreserver ( ) { new Thread (this) . start ( ) ;
} // constructor ( )
public synchronized void run () { try {
|
wait ( ) ; |
} |
catch (InterruptedException е) { |
} |
// try |
// |
run ( ) |
/** |
|
*Собранная сборщиком мусора и переданная этому методу
*коллекция объектов будет сохранена до тех пор ,
* пока объекты не будут переданы методу unpreserveObj ect .
* / |
|
|
public static void preserveObject (Object о) |
{ |
|
|
protectedSet . add (o) ; |
|
} |
// preserveObj ect ( ) |
|
/* * |
|
|
* |
Объекты, переданные этому методу, теряют |
защиту от сборщика |
* |
мусора . |
|
* /
public static void unpreserveObject (Object о) { protectedSet . remove (o) ;
//unpreserveObj ect (Object)
//class Obj ectPreserver
Singleton 8 159
IИ объект класса, инкапсулируюший класс или один из экземпляров класса, lедается методу preserveObject класса ObjectPreserver, представленному оследнем листинге, то этот класс не будет удален при сборке мусора.
1ЕДСТВИЯ
Сушествует строго один экземпляр класса-одиночки.
eTOД getInstance класса-одиночки инкапсулирует политику создания для класса-одиночки, поэтому классы, используюшие класс-одиночку, не зависят от деталей его инстанциирования.
Другие классы, которые хотят ссылаться на один экземпляр класса-одиночки, должны получать этот экземпляр, вызывая статический метод getInstance класса, а не создавать экземпляр самостоятельно.
Образование подклассов от класса-одиночки вызывает затруднения и при водит к неправильно инкапсулированным классам. Чтобы создать подкласс класса-одиночки, необходимо объявить в нем конструктор, который не будет JaKpblTblM. Кроме того, поскольку статические функции не могут быть за l1:ешены, подкласс класса-одиночки должен оставлять метод getInstance ;воего суперкласса открытым.
ИМЕНЕНИЕ В JAVA API
;с Java API с именем j ava . lang . Runtime - это класс-одиночка. Он имеет го один экземпляр. У него нет открытых конструкторов. Чтобы получить
,ку на единственный его экземпляр, другие классы должны вызывать его -t:ческий метод getRuntime.
е MEP КОДА
показан реализованный на языке Java класс, который можно использо lI.ЛЯ запрета одновременного воспроизведения двухК аудиоклипов. Класс яв :я классом-одиночкой. ожно получить доступ его экземпляру, вызывая га-1 тический метод getInstance. Если вы проигрываете аудиоклип при по \ такого объекта, он прерывает воспроизведение последнего аудиоклипа началом воспроизведения нового аудиоклипа. Если все аудиоклипы вос ЗВОДЯТСЯ через объект AudioClipManager, то в любой момент времени гда не будет воспроизводиться более одного аудиоклипа.
public сlавс AudioClipМanager implements AudioClip ( private= static AudioClipМanager instance
new AudioClipМanager () ;
pr1vate AudioClip prevClip; // Предыдущий аудиоклип .
160 • Глава 5. Порождающие шаблоны проектироваНИR
/ ......
... Объявляем закрытьrn конструктор . Таким образом,
... компилятор не будет создавать oTKpЫTьrn конструктор
... по умолчанию .
.../
private AudioClipМanager () ( )
/"''''
'" Возвращает ссылку на единственньrn экземпляр этого класса .
" ' /
public static AudioClipМanager getInstance () ( return instance i
Singleton • 161
puы1cc |
void stop () |
{ |
if |
(preVClip ! = |
null) |
preVClip . stop () ;
// s top ( )
// class AudioCl ipManager
ШАБЛОНЫ ПРОЕКТИРОВАНИЯ,
СВЯЗАННЫЕ С ШАБЛОНОМ SINGLETON
Шаблон Singleton можно использовать со многими другими шаблонами. В ча истности, он часто применяется вместе с шаблонами Abstract Factory, Builder
Prototype.
СасЬе Management. Шаблон Singleton имеет некоторое сходство с шаблоном проектирования Cache Management. С точки зрения функциональности шаб лон Singleton подобен шаблону Cache, содержащему только один объект.
Object Pool. Шаблон Object Pool предназначен для управления любой большой коллекцией похожих объектов, а не только одним объектом.
СИНОПСИС
Управляет повторным использованием объектов в случаях, когда создание объ,. екта требует больших затрат или может быть создано только ограниченное ко. личество объектов некотороro вида.
КОНТЕКСТ
Предположим, нужно написать библиотеку классов ДЛЯ предоставления дocCQТJ-. па к базе данных. Клиенты отправляют запросы к базе данных через сетевое единение. Сервер базы данных получает и возвращает запросы через то же сета· вое соединение.
Чтобы программа могла отправлять запросы базе данных, она должна иметь с ней соединение. Для программистов, которые будут пользоваться библиоте· кой, самый подходящий способ управления соединениями состоит в том, что· бы каЖдая часть программы, нуждающаяся в соединении, создавала собствен ное соединение. Однако создание излишних соединений с базой данных,
•которые не потребуются на практике, нежелательно по нескольким причинам:
•времени; чем больше соединений, тем больше требуется времени для создания новЫХ
•соединений; каЖдое соединение с базой данных использует сетевое соединение. НекоТО
рые платформы накладывают ограничения на количество разрешенных ими сетевых соединений.
Проект библиотеки должен примирить следующие конфликтующие стороны. Необходимость обеспечить удобный API ДЛЯ программистов «тянет» проеКТ в одну сторону. Большие затраты на создание объектов соединений с базоit данных и, кроме того, возможное ограничение на количество oДHoBpeMeHHьV' соединений «тянут» проект в другую сторону. Разрешить это противоречие воЗ можно в том случае, если библиотека будет управлять соединениями с базоit данных.
Стратегическое решение, используемоечтобиблиотекой для управления соедине ниями, основано на предположении, соединения с базой данных програМ
мы являются взаимозаменяемыми. Пока соединение находится в состояниJl.
позволяющем ему передавать запросы к базе данных, неважно, какое из соедJl-
Object Pool • 165
РЕШЕНИЕ
Если экземпляры некоторого класса можно многократно использовать, избе гайте создания новых экземпляров этого класса и используйте их повторно.
На рис. 5. 19 представлена диаграмма классов шаблона Object Ро01.
опишем роли, исполняемые классами в шаблоне Object Ро01.
ReusabIe. Экземпляры классов в этой роли взаимодействуют с другими объек
тами в течение ограниченного времени, а затем они больше не нужны для этого
взаимодействия.
C1ient. Экземпляры классов в этой роли используют объекты Reusable.
Экземпляры классов в этой роли управляют объектами Reusable,
предназначенными ДЛЯ использования объектами Client. Как правило, жела тельно поддерживать в одном и том же пуле все объекты Reusable, которые не используются в данный момент, чтобы ими можно было управлять в соответст
вии с единой последовательной политикой. Для достижения этой цели класс
ReusablePool проектируется как класс-одиночка. Его конструктор(ы) является закрытым, что вынуждает другие классы обращаться к его методу getlnstance за получением одного экземпляра класса ReusablePool.
|
I |
Cliепt |
I 0 .* |
Управляет объектами многократного использования |
||||||||
|
J клиент. |
|
|
|
|
|||||||
|
1 |
|
|
|
|
|
управляющий |
v 1 |
||||
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
ReusablePool |
|
|
|
|
Использует ...... |
|
|
|
|
«сопstгuctоl'» |
|
|
|
|
|||
|
|
|
|
-RеusаblеРооl |
|
|
|
|
||||
|
|
|
|
|
|
|
|
«misc» |
|
|
|
|
|
|
|
|
|
|
|
|
+gеtIпstапсе() |
|
|
||
|
|
|
|
|
|
|
|
+acquireReusableO:Reusable |
|
|
||
|
|
|
|
|
|
|
|
HeleaseReusable(:Reusable) |
|
|
||
|
|
|
|
|
|
|
|
+setMaxPool5ize(maxSize:int) |
|
|
||
|
|
* |
|
|
|
|
|
. . . |
(> 1 |
|
|
|
|
|
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
l |
Reusable |
|
l'L0. . * |
Рис. 5.19. Шаблон Object Pool |
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|||
Если объекту Client |
потребуется объект Reusable, то он |
вызовет метод |
acquireReusable объекта ReusablePool. ОбъектДЛЯReusablePool поддержива
ет комекцию объектов Reusable И применяет ее поддержки пула объектов
Reusable, не использующихся в данный момент. Если при вызове метода