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

GrandM-Patterns_in_Java

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

156 Глава 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

объект класса, инкапсулируюший класс или один из экземпляров класса, 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; // Предыдущий аудиоклип .

// getlnstance ( )
/. .
Останавливаем предыдущий аудиоклип
и воспроизводим заданньrn аудиоклип .
'" @param clip Новый аудиоклип, KOTopьrn должен проигрываться .
"' /
public void play (AudioClip clip) { if (preVClip ! = null)
preVClip . stop ( ) ; preVClip = clipi сliр . play () i
} // play ( AudioClip)
/ " ''''
'" Останавливаем предыдущий аудиоклип
'" и воспроизводим данньrn аудиоклип в цикле .
'" @pa ram cl ip HOBьrn аудиоклип, который должен проигрываться .
. /
public void loop (AudioClip clip) { if (preVClip ! = null)
preVClip . stop () i
preVClip = clipi сliр . loop () i
} / / play (AudioC lip)
/ " ' "
'" Останавливаем воспроизведение этого аудиоклипа .
.../

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

СИНОПСИС

Управляет повторным использованием объектов в случаях, когда создание объ,. екта требует больших затрат или может быть создано только ограниченное ко. личество объектов некотороro вида.

КОНТЕКСТ

Предположим, нужно написать библиотеку классов ДЛЯ предоставления дocCQТJ-­. па к базе данных. Клиенты отправляют запросы к базе данных через сетевое единение. Сервер базы данных получает и возвращает запросы через то же сета· вое соединение.

Чтобы программа могла отправлять запросы базе данных, она должна иметь с ней соединение. Для программистов, которые будут пользоваться библиоте· кой, самый подходящий способ управления соединениями состоит в том, что· бы каЖдая часть программы, нуждающаяся в соединении, создавала собствен­ ное соединение. Однако создание излишних соединений с базой данных,

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

времени; чем больше соединений, тем больше требуется времени для создания новЫХ

соединений; каЖдое соединение с базой данных использует сетевое соединение. НекоТО­

рые платформы накладывают ограничения на количество разрешенных ими сетевых соединений.

Проект библиотеки должен примирить следующие конфликтующие стороны. Необходимость обеспечить удобный API ДЛЯ программистов «тянет» проеКТ в одну сторону. Большие затраты на создание объектов соединений с базоit данных и, кроме того, возможное ограничение на количество oДHoBpeMeHHьV' соединений «тянут» проект в другую сторону. Разрешить это противоречие воЗ­ можно в том случае, если библиотека будет управлять соединениями с базоit данных.

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

мы являются взаимозаменяемыми. Пока соединение находится в состояниJl.

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

Object Pool 163

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

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

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

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

Объект Connection образует пару с объектом Connectionlmpl только в тече­

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

получения результата. Объекты Connectionlmpl инкапсулируют реальное соединение с базой данных.

Библиотека создает и управляет объектами Connectionlmpl. Управдля ление объек­ тами Connectionlmpl осуществляется путем поддержки пула тех объектов, которые не образуют в данный момент пару с объектом Connection. Библиотека создает объект Connectionlmpl только тогда, когда должна образоваться еще одна пара этого объекта с объектом Connection, а пул объектов Connectionlmpl

пуст. На диаграмме классов, представленной на рис. 5.18, изображены классы, участвующие в управлении пулом объектов Connectionlmpl.

Соппесооп

databaseName:String

. . .

Использует ....

... 0. .1

ConnectionlmpL

0 *

Управляет объектами ConnectionImpL

 

 

кл• .иент

 

1

 

 

 

управляющий

 

 

 

ConnectionPooL

 

 

 

 

«constructor»

 

 

 

 

-СоппеctiопРооL()

 

 

 

 

«misc»

 

 

 

 

+getInstance()

 

 

 

 

+acquireImpL(databaseName:String ):ConnectfonImpL

 

 

..

 

 

 

 

 

+reLeaselmpL(:ConnectionlmpL).

 

 

0. .*

 

О

 

 

1

 

 

 

 

 

 

 

Рис. 5.18. Управление пулом объектов Connectionlmpl

Если объекту Connection нужен объект Connectionlmpl, он вызывает метод

Acquirelmpl объекта ConnectionPool, передавая ему имя базы данных, с ко­

Торой должно быть установлено соединение. Если в коллекции объектов

ConnectionPool есть какие-либо объекты Connectionlmpl, связанные с нуж-

1 64 Глава 5. Порождающие шаблоны проектирования

ной базой данных, то он возвращает один из этих объектов. Если в коллекции

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

объекта Connectionlmpl, он ожидает до тех пор, пока существующий объект Connectionlmpl не возвратится в пул посредством вызова метода release­ Impl, а затем он возвращает этот объект.

Класс ConnectionPool - это класс-одиночка. Должен существовать только один экземпляр класса ConnectionPool. Конструктор класса является закры­

тым. Другие классы обращаются к единственному экземпляру класса Соппес­ tionPool, вызывая его статический метод getlnstance.

Существует множество причин, не позволяющих методу Acquirelmpl объекта ConnectionPool создавать объект Connectionlmpl. Одной из таких причин может быть существование ограничения на количество объектов Соппес­ tionlmpl, создаваемых Д1lЯ подключения к одной и той же базе данных. Причи­ ной этого ограничения является гарантия того, что база данных может поддер­ живать некоторое минимальное количество клиентов. Существует максималь­ ное количество соединений, которое может поддерживать каждая база данных, поэтому ограничение количества соединений каждого клиента с базой данных позволяет гарантировать поддержку некоторого минимального количества кли­ ентских программ.

моти в ы

©Программа может создавать только ограниченное количество экземIUlЯРОВ

некоторого класса.

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

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

использовать объекты, с которыми она уже завершила работу, и не позво­

лять сборщику мусора удалять их.

©Экземпляры класса являются взаимозаменяемыми. Если есть сразу не­

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

в своих целях.

©Ресурсами можно управлять централизованно, с помощью одного объекта,

или децентрализованно, с помощью нескольких объектов. Проще управ­

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

©Некоторые объекты потребляют такие ресурсы, запасы которых ограниче­ ны. Другие объекты могут потреблять много памяти. Третьи объекты могут

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

пользовать такой ресурс, когда сам объект не используется.

ReusablePool.

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, не использующихся в данный момент. Если при вызове метода

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