GrandM-Patterns_in_Java
.pdfЭтот шаблон ранее был описан в работе [GoF95].
СИНОПСИС
Шаблон Bridge полезен в случаях, когда существует иерархия абстракций и со ответствующая иерархия реализаций. Шаблон Bridge не комбинирует абстрак ции и реализации в виде множества отдельных классов, а реализует их в виде независимых динамически объединяемых классов.
КОНТЕКСТ
Предположим, что нужно написать классы, которые осуществляют доступ к фи
зическим сенсорам, контролирующим приложения. Сенсорами будем считать некоторые устройства, например, шкалы, устройства измерения скорости и дат чики определения позиции. Общим для всех этих устройств является то, что они выполняют физическое измерение и вьщают значение. Отличаются эти устрой ства• только типом производимых ими измерений.
Шкала выдает единственное число, являющееся результатом измерения
•в одной точке в некоторый момент времени.
Устройство измерения скорости производит единственное измерение, пред-
•ставляющее собой среднее значение за какой-то период. Датчик определения позиции выдает поток измерений.
Это означает, что такие устройства могут поддерживаться тремя классами, со
ответствующими различным способам измерений (рис. 7.6).
Рис. 7.6. Классы сенсоров
Три данных класса описывают чистые абстракции, которые могут применяться
Не только к трем вышеназванным, но и к сенсорам многих других типов. Суще Ствуютс сенсоры других типов, выполняющие простые измерения, измерения
усреднением по времени и потоки измерений, поэтому можно многократно
228 • Глава 7. Структурные шаблоны проектирования
использовать эти классы для датчиков таких типов. Проблема, связанная с pea лизацией подобного многократного использования, состоит в том, что деталg установления связи с сенсорами, сделанными различными производитеЛЯМIt, отличаются друг от друга. Предположим, что создаваемое ПО должно работать с сенсорами двух производителей: Eagle и Hawk. Решение может быть найдено, если каждому производителю будет соответствовать свой класс (рис. 7.7) .
Рис. 7.7. Классы сенсоров, зависящие от производителей
Еще одна проблема заключается не только в невозможности многократного применения классов для простых, усредняющих и потоковых датчиков. При использовании других классов также обнаруживаются различия между произ водителями, что делает такие классы менее пригодными для многократного ис пользования. Задача состоит в том, как представить иерархию абстракций, что
бы абстракции поддерживались независимо от их реализаций.
С этой целью следует добавить косвенность, которая должна защитить иерар хию классов, поддерживающихчто абстракции, от классов, реализующих эти абст ракции. Имеется в виду, классы абстракций должны иметь доступ к классам реализаций через иерархию интерфейсов реализаций, параллельную иерархии абстракций (рис. 7.8).
Согласно представленному на данном рисунке решению проблема состоит из трех• иерархий, которые отделены друг от друга горизонтальными линиями:
вверху располагается иерархия классов сенсоров, не зависящих от произво
•дителя;
внизу располагаются параллельные иерархии классов, зависящих от произ
•водителя;
в центре располагается параллельная иерархия интерфейсов, которые по
зволяют классам, не зависящим от производителя, оставаться независиМЫ ми от любых классов, специфических для производителя.
Логика, общая для сенсора любого типа, будет находиться в классе, не завиСЯ щем от производителя. Логика, отражающая специфику производителя , попа дет в класс, который зависит от производителя.
Большая часть рассматриваемой в этом примере логики предназначена длЯ управления исключительными ситуациями. Примером такой ситуации можef
быть следующее: простой сенсор обнаруживает выходящее за пределы диапаЗОf{01а
значение, слишком большое, чтобы его можно было измерить. Не зависящее
Bridge • 231
вующий интерфейс-наследник интерфейса Abstractionlmpl. Кажд.ыЙ класс specializedAbstraction делегирует свои операции объекту реализации, ко торый реализует интерфейс Speciali zedAbstractionlmpl, соответствующий классу Speci a l i zedAbstraction.
AЬstractionImpl. Этот интерфейс объявляет методы ДЛЯ всех операций нижнего уровня, которые должны предоотвес авляться реализацией класса Abstraction.
SpecializedAbstractionImpl. Он с тствует интерфейсу-наследнику интерфейса
Abstractionlmpl. Кажд.ыЙ интерфейс Special i zed.Abstractionlmpl соот ветствует классу Special i zed.Abstraction и объявляет методы ДЛЯ операций нижнего уровня, необходимых ДЛЯ реализации этого класса.
Impll, Impl2. Эти классы реализуют интерфейс Abstractionlmpl И обеспечи вают различные реализации класса Abstraction.
SpecializedImpll, SpecializedImpl2. Эти классы реализуют один из интерфейсов Speciali zedAbstractionlmpl и обеспечивают различные реализации класса
Specia l i zedAbstraction.
На рис. 7.9 представлены интерфейсы реализации абстракций, имеющие те же методы, что и соответствующие классы абстракций. Это сделано просто ДЛЯ на глядности. Интерфейсы реализации абстракций могут содержать методы, отли чающиеся от методов соответствующих классов абстракций.
При реализации шаблона Bridgeдлявсегда должна решаться следующая задача: как создавать объекты реализации кажд.ого объекта абстракции. Наиболее час
то встречаются решения, при которых объекты абстракций создают свои собст
венные объекты реализаций либо делегируют создание этих объектов реализа
ций другому объекту.
Самым оптимальным обычно является второй вариант, при котором объекты абстракций делегируют создание объектов реализаций. При этом сохраняется
взаимная независимость классов абстракций и реализаций. Если классы абст ракиий должны делегировать создание объектов реализаций, то ДЛЯ создания объектов реализаций в проекте обычно используетсяДЛЯ шаблон Abstract Factory.
Однако если количество классов реализаций абстрактного класса очень Мало и набор классов реализаций, как предполагается, не должен изменяться,
то приемлемая оптимизация заключается в том, чтобы заставить класс абстрак ции создавать свои собственные объекты реализации.
С этой проблемой связано принятиежееще одного решения: будет ли объект аб СТракции использовать один и тот объект реализации в течение времени Жизни. По мере изменения используемых шаблонов или других условий может
быть уместно изменение объекта реализации, используемого объектом абст
Ракиии. Если класс абстракиии прямым образом создает собственные объекты
Реализаций, то лучше поместить логику, отвечающую за изменение объекта
232 • Глава 7. Структурные шабланы праектирования
реализации, прямо в класс абстракции. В противном случае можно использо
вать шаблон Decorator с целью инкапсуляции логики, предназначенной для пе
реключения объектов реализации, в классе-обертке (wrapper class).
СЛЕДСТВИЯ |
|
© |
Шаблон Bridge поддерживает классы, представляющие абстракцию, неза |
|
висимо от классов, реализующих ее. Абстракция и ее реализации организо |
|
ваны в виде отдельных иерархий классов. Можно расширить каждую иерар |
|
хию классов без непосредственного воздействия на другую иерархию клас |
|
сов. Один класс абстракции может иметь множество классов реализаций, |
|
или несколько классов абстракций могут использовать один и тот же класс |
© |
реализации. |
|
Классы, которые являются клиентами классов абстракций, не обладают ка кой-либо информацией о классах реализаций. Поэтому объект абстракции может изменять свою реализацию, не оказывая никакого влияния на своих клиентов.
ПРИМЕНЕНИЕ В JAVA API
Java API содержит пакет j ava . awt, В котором находится класс Component. Он представляет собой абстрактный класс, инкапсулирующий общую для всех компонентов GUI логику. Класс Component имеет подклассы, например,
Button, List и TextField, которые инкапсулируют логику ДJIЯ соответствую
щих компонентов GUI, не зависящую от платформы. В пакете j ava . awt . peer
есть также интерфейсы, например, ComponentPeer, ButtonPeer,д.тIЯ ListPeer
И TextFieldPeer, которые объявляют методы, необходимые классов реа
лизаuий, обеспечивающих зависящую от платформы поддержку подклассов
класса Component.
Подклассы класса Component используют шаблон Abstract Factory Д Л Я созданиЯ
своих объектов реализаций. Класс j ava . awt . Toolkit - это абстрактный
класс, исполняющий роль абстрактной фабрики. В зависимости от платформЫ
предоставляются классы реализаций и класс конкретной фабрики, используе
мый для инстанциирования классов реализаций.
ПРИМЕР КОДА
в качестве примера шаблона Bridge рассмотрим код, реализующий классы, свТОЯ
занные с сенсорами и рассмотренные в разделе «Контекст». Предположим, ч
объекты, представляющие сенсоры и их реализации, создаются при ПОМOlI1И шаблона Factory Method. Объект Factory Method знает, какие сенсоры дос тупны и какие объекты должны создаваться для предоставления доступа к сеН сору, а также создаст такие объекты при первом же запросе на получение доступа к сенсору.
Bridge _ 233
f1риведем код для класса SimpleSensor, исполняющего роль класса абстракции:
public class SimpleSensor (
/ / |
Ссылка на объект, который реализует операции, |
|
/ / |
специфические для реального сенсорного устройства , |
|
/ / |
представленного этим объектом . |
|
private SimpleSensorImpl impl ; |
|
|
/ * * |
|
|
* |
@param impl |
|
* |
Объект , который реализует операции, |
зависящие о т типа |
* |
сенсора . |
|
* / |
|
|
SimpleSensor (SimpleSensorImpl impl) |
|
|
|
this . impl = impl ; |
|
} |
/ / cons tructor ( S impleSensorlmpl) |
|
protected SimpleSensorImpl getImpl () |
|
|
|
return impl; |
|
} |
/ / get lmpl ( ) |
|
/ * * |
|
|
* |
Возвращает значение , которое является |
текущим измерением |
сенсора . |
|
* /
public int getValue ( ) throws SensorException ( return impl . getValue () ;
// getValue ( )
// class S impleSensor
Как следует из названияl, класс SimpleSensor является ПРОСТb l М . Он делает Немного больше, чем просто делегирует свои операции объекту, реализующему
Интерфейс SimpleSensorlmpl. Приведем код интерфейса SimpleSensorlmpl:
interface SimpleSensorImpl
/ * * * Возвращает значение , которое является текущим измерением
*сенсора .
*/
public int getValue () throws SensorException;
/ / interface S impleSensorlmpl
-----------------------------англ.
От simple -- простой. (Примеч. ред.)
234 • Глава 7. Структурные шаблоны проектирования
Некоторые подклассы класса SimpleSensor обладают такой же простой струк турой. Приведем код класса AveragingSensor. Экземпляры этого класса пред. ставляют сенсоры, которые выдают значения, определяющие среднее всех из. мерений, выполненных в течение некотороro периода времени.
public class AveragingSensor extends SimpleSensor {
/* * |
@param impl |
|
* |
Объект, реализующий операции, |
зависящие от типа сенсора . |
* / |
|
|
AveragingSensor (AveragingSensorImpl |
impl) { |
|
super (impl) ; |
|
// con s t ructor (AveragingSensorlmpl )
/ * *
* Усредняющие сенсоры выдают значение , которое является
средним всех измерений, выполненных в течение некоторого
*периода .
*Этот период начинается с момента вызова этого
*метода .
*/
public void beginAverage () throws SensorException {
« AveragingSensorImpl) getImpl ( » .beginAverage () ;
//beginAverage ( )
//class AveragingSensor
Нетрудно заметить, что класс AveragingSensor является очень простым, он делегирует свои операции используемым объектам реализации.
interface AveragingSensorImpl extends SimpleSensorImpl
/**
* Усредняющие сенсоры выдают значение, которое является
* средним всех измерений, выполненных в течение некоторого
*периода .
Этот период начинается с момента вызова этого
*метода .
*/
public void beginAverage (} throws SensorException ;
// interface Ave ragingSensorlmpl
Bridge _ 235
Подклассы класса SimpleSensor могут быть более сложными и предоставлять
собственные дополнительные сервисы. Класс StreamingSensor передает по ток измерений объектам, зарегистрировавшимся в качестве получателей этих
чатьItэмерениЙ. Он передает измерение методу того объекта, который должен полу
измерение. Он никак не ограничивает время, которое потребуется на выпол tlение метода. Здесь сушествует простое предположение, что время выполнения tdетода будет вполне приемлемым. С другой стороны, объекты реализации, ис впользуемые вместе с экземплярами класса StreamingSensor, могут нуждаться т предоставяНЫ. лении измерений с постоянной скоростью, или же данные будут по ер Чтобы предотвратить потерю измерений, экземпляры класса Stream ingSensor помещают предоставляемые им данные измерений в буфер, откуда эТДЛЯИ данные асинхронно передаются другим объектам. Теперь рассмотрим код
класса StreamingSensor:
puыlcc class StreaminqSensor extends SimpleSensor implements StreaminqSensorListener , Runnable
// Эти объекты предоставляют буфер , позволяющий объекту
//реализации асинхронно передавать измеренные значения в то
// |
время, когда этот объект передает уже полученное значение |
// |
своим приемникам . |
private DataInputStream consumer; |
|
private DataOutputStream producer; |
|
// |
Коллекция приемников . |
private Vector listeners = new Vector ( ) ;
/* *
* @param impl
* Объект, KOTOPbrn реализует операции, зависящие от типа
*сенсора и предоставляемые этим объектом .
*/
StreaminqSensor (StreaminqSensorImpl impl)
|
throws SensorException ( |
super (impl) ; |
|
/ / |
Создает канальный поток, KOTOPbrn будет поддерживать |
// |
способность этого объекта передавать измеренные данные |
/ / |
одновременно с их получением . |
PipedInputStream pipedInput = new PipedInputStream() ; |
|
consumer = new DataInputStream (pipedInput) ; |
|
PipedOutputStream pipedOutput; |
|
try |
( |
pipedOutput = new PipedOUtputStream(pipedInput) ;
6 |
• Глава 7. Структурные шаблоны проектирования |
|
|
|
catch (IOException е) ( |
|
throw new SensorException (<<pipe creation failed») ; |
|
// try |
|
producer = new DataOutputStream(pipedOutput) ; |
|
// Запускаем поток для передачи измеренных значений . |
|
new Thread (this) . start () ; |
|
// cons tructo r (S treamingSensorlmpl ) |
/**
* Потоковые сенсоры выдают поток измеренных величин .
* Частота выдачи потока значений не превышает заданное
*количество раз в минуту .
@param freq
* |
Максимальная частота (раз в минуту) выдачи результатов |
* |
измерений данным потоковым сенсором . |
*/ |
|
public void setSamplingFrequency (int freq)
throws SensorException {
// Делегирует это объекту реализации .
StreamingSensorImpl= impl ;
impl (StreamingSensorImpl)getImpl () ;
impl . setSamplingFreguency (freq) ; // setSamplingFrequency ( int)
/**
*Объекты StreamingSensor предоставляют поток значений
*заинтересованным в этом объектам,
*передавая каждое значение методу processMeasurement
*объекта . Передача значений осуществляется при помощи
* собственного потока и |
является полностью асинхронной . |
|||
* @param value Передаваемое измеренное |
значение . |
|||
* / |
|
|
|
|
public void processМeasurement(int value) |
|
|||
try |
|
|
|
|
|
producer .writeInt (value) ; |
|
||
|
catch (IOException е) |
{ |
|
|
// |
Не |
может передать значение , просто |
отбрасывает его . |
|
} |
/ / |
try |
|
|
// proces s easurement ( int)