GrandM-Patterns_in_Java
.pdfAdapter - 217
В нутр енние классы
Язык Java позволяет использовать в программах вложенные объявления классов, например,
public class FooХ ; private int
class Bar (
Хincrement ()
=х+l ;
}// increment ( )
// class bar
//class foovoid
Поскольку класс Bar задан внутри класса Foo, он считается частью клас са Foo и поэтому может ССЬVIаться на закрытые переменные экземпляры класса Foo. Когда экземпляр класса Foo создает экземпляр класса Bar, экземпляр класса Foo, который создал объект Bar, вызывается включаю щим его экземпляром класса Bar. Любые ССЬVIки в классе Bar на одну из переменных экземпляра класса Foo будут указывать на переменные включающего его экземпляра.
Внутренние классы могут быть закрытыми. Если некоторый класс нужен только для поддержки другого класса, то самый лучший способ организа ции поддерживающего класса может состоять в том, чтобы сделать его за Крытым внутренним классом того класса, который он поддерживает.
Если объект должен иметь другой объект, вызывающий один из его мето дов, чтобы сделать это, он передает объект-адаптер другому объекту; в та ком случае объект-адаптер вполне может быть экземпляром закрытого
внутреннего класса.
Внутренний класс может быть объявлен внутри метода, тогда он может Получить доступ к переменным экземпляра содержащего его класса и ло
кальным переменным содержащего его метода. |
|
|
|
Внут |
могут быть анонимными. |
||
ренние классы, объявленные в методе, |
|||
Примером такого класса может служить класс, |
описанный |
в разделе |
|
« Реализацию>: |
|
|
|
МenuItem exit = new MenuItem (caption) ; |
|
|
|
exit. addActionListener (new ActionListener () public void actionPerformed(ActionEvent evt)
8• Глава 7. Структурные шаблоны проектирования
close ( ) ;
} / / actionPerformed (ActionEvent )
) ;
Синтаксис языка одинаков как при создании экземпляра класса, так и при определении класса. Синтаксис предусматривает использование
слова «new», за которым следует имя расширяемого класса или реализуе мого интерфейса, аргументы конструктора и, наконец, тело класса в фи
гурных скобках.
Представленный пример определяет анонимный внутренний класс, ко торый реализует интерфейс ActionLi stener. Он создает экземпляр
анонимного класса и передает его методу addActionLis tener объекта
Menul tem.
Более подробная информация о внутренних классах содержится в п. 8. 1 .2 « Спецификации языка Java» на сайте http://java.sun.com/docsjbooks/jls/ second_editionjhtmljjTOC.doc.html.
lЕДСТВИЯ
Классы Adaptee и Client остаются независимыми друг от друга.
Класс-адаптер можно использовать для определения, какой из методов объекта вызывается другим объектом. Предположим, есть класс, экземпля ры которого представляют собой элементы управления окном GUI, позво
ляющие отображать на экране и редактировать телефонные номера. этот
класс считывает и сохраняет телефонные номера, вызывая методы, опреде
ляемые интерфейсом. Чтобы использовать интерфейс, необходимо опреде
лить классы-адаптеры. Один класс-адаптер можно использовать для счи
тывания и хранения номера факса из экземпляров класса, а другой класс -
для считывания и хранения номеров пейлжера из экземпляров того ЧТОже
класса. Различие меЖдУ двумя классами адаптеров заключается в том,
они вызывают различные методы класса Adaptee. Рассмотрим на примере.
Предположим, что существует класс PhoneNumberEdi tor, который отве
чает за то, чтобы разрешить пользователю изменять телефонный номер·
Объект передается конструктору класса PhoneNumberEdi tor, которы й
реализует этот интерфейс.
public interface PhoneNumberIF public Strinq qetPhoneNumber ( )
public void setPhoneNumber (Strinq newValue)
/ / inter face PhoneNumberIF
Adapter 8 219
Если нужно создать два объекта PhoneNumberEditor для изменения офис ных телефонных и факсовых номеров какого-то человека, то можно напи сать примерно такой код:
PhoneNumberEditor= voiceNumber;
voiceNumber new PhoneNumЬerEditor (new PhoneNumberIF () ( public Strinq qetPhoneNumЬer ( ) (
return person . qetOfficeNumЬer () ; ) // getPhoneNumber
public void setPhoneNumЬer (Strinq newValue) person . setOfficeNumЬer (newValue) ;
// setPhoneNumber ( String)
} )
PhoneNumberEditor faxNumЬer ;
faxNumber = new PhoneNumЬerEditor (new PhoneNumЬerIF () ( public Strinq qetPhoneNumber() (
return person . qetFAXNumЬ er () ;
}// getPhoneNumber
public void setPhoneNumber(Strinq newValue) ( person . setFAXN umЬer (newValue) ;
}// set PhoneNumber ( String)
}) ;
Каждый объект PhoneNumberEdi tor создается с новым адаптером. Каж дый адаптер вызывает разные методы одного и того же объекта.
€) Шаблон Adapter добавляет в проrpамму косвенность. Подобно любой другой косвенности, это усложняет понимание такой проrpаммы.
ПРИМЕНЕНИЕ В JAVA АР' И ПРИМЕР КОДА
Распространенный способ использования классов-адаптеров в Java API, пред
Назначенный для обработки события, выглядит примерно так:
Button ok = new Button ("OK") ;
ok . addActionListener (new ActionListener () public void actionPerformed(ActionEvent evt)
dolt () ;
}// actionPerformed (ActionEvent )
}) ;
add (ok) ;
220• Глава 7. Структурныешаблоны проектирования
вэтом примере кода создается экземпляр анонимного класса, реализующий интерфейс ActionListener. При нажатии кнопки (объект Button) вызывает ся метод actionPerformedДЛЯэтого класса. Этот шаблон широко pacnpoCтpaHel{
вкодах, предназначенных обработки событий.
Java АРI не содержит каких либо oTKpbrrbIXj классов-адаптеров, готовых к ис пользованию. Он имеет классы, например ava . awt . event . WindowAdapter, предназначенные не для прямого использования, а для создания на их основе подклассов. Идея состоит в том, что некоторые интерфейсы приемника собы тий, например WindowListener, объявляют множество методов. Как правило, не все эти методы должны быть реализованы. Интерфейс WindowListener объявляет восемь методов, которые вызываются для оповещения о различных событиях, связанных с окнами. Часто только события одного или двух видов представляют интерес. Методы, соответствующие событиям, не представляю щим интерес, обычно делают пустыми. Класс WindowAdapter реализует ин терфейс WindowLi s tener и реализует все восемь его методов как бездействую щие. Класс адаптера, являющийся подклассом класса WindowAdapter, должен реализовывать только методы, соответствующие представляющим интерес со бытиям. Для всех остальных методов он наследует бездействующие варианты реализации. Например:
addWindowListener (new WindowAdapter () public void windowClosinq (WindowEvent е) (
Syster n . exit () ;
// windowClos ing (WindowEvent )
);
вданном примере кода анонимный класс-адаптер является подклассом класса WindowAdapter. Он реализует только метод windowClos ing и наследует от класса WindowAdapter бездействующие варианты реализации остальных семИ методов.
ШАБЛОНЫ ПРОЕКТИРОВАНИЯ, СВЯЗАННЫЕ С ШАБЛОНОМ ADAPTER
Facade. Класс Adapter предоставляет объект, действующий как промежуточ ное звено при обращениях к методам, осуществляемых между клиентскиМ"
объектами и одним другим объектом, который не известен клиентским объеК там. Шаблон Facade предоставляет объект, действующий как промежуточное звено при обращениях к методам, осуществляемым между клиентскими объеК
тами и несколькими объектами, не известными клиентским объектам.
Iterator. Шаблон Iterator представляет собой специальную версию шаблоtJ8 Adapter, предназначенную для последовательного доступа к содержимому коЛ лекции объектов.
Adapter - 221
proxy. Шаблон Proxy, подобно шаблону Adapter, использует объект, который flвляется заменителем другого объекта. Однако объект Proxy имеет тот же ин терфейс, что и объект, заменителем которого он является.
Strategy. С точки зрения структуры шаблон Strategy аналогичен шаблону Лdарtег. Различие состоит в предназначении. Шаблон Adapter позволяет объекту Client выполнять при взаимодействии свою изначально предопределенную функцию, вызывая методы объектов, реализующих определенный интерфейс.
Шаблон Strategy предоставляет объекты, реализующие определенный интер фейс, с целью изменения или определения поведения объекта Client.
Anonymous Adapter. Шаблон Anonymous Adapter (описанный в книге [Grand99])
представляет собой шаблон кодирования, который использует анонимные объекты-адаптеры для управления событиями.
РЕАЛИЗАЦИЯ
До п ол н ител ь н ы е методы
Интерфейс объекта-итератора, представленный в разделе « Решение», содеРЖl1t минимальный набор методов. Интерфейсы объектов-итераторов, как правило, определяют дополнительные методы, если они нужны и если поддерживаются базовой коллекцией классов. Кроме методов, проверяющих наличие и считываю
щих следующий элемент коллекции, часто используются следующие методы: |
|
• |
проверка наличия и считывание предыдущего элемента коллекции; |
• |
перемещение к первому или последнему элементу коллекции; |
• |
получение количества элементов обхода.
Внутре н н и й кл а сс
Во многих случаях алгоритм обхода класса-итератора требует доступа к внyr ренней структуре данных класса коллекции. По этой причине классы-итера торы часто реализуются в виде закрытого внутреннего класса, принадлежащеro классу коллекции.
Пусто й ите р ато р
Пустой итератор - это итератор, который не возвращает никаких объектов. Его метод hasNext всегда возвращает false. Пустые итераторы обычно пред ставляют собой простой класс, который реализует соответствующий интерфейс итератора. Использование пустых итераторов может упростить реализаЦi1Ю классов коллекции и других классов итераторов, так как в этом случае нет неу обходимости в коде, предназначенном для обработки специального случая левого обхода.
Изме н е н и е б а з о в о й колл е к ц и и
При изменении коллекции во время обхода итератором ее содержимого могут возникнуть проблемы. Если при выполнении таких изменений не принять мрер предосторожности, то итератор может возвратить несогласованный набор зультатов. Возможны следующие ошибки: пропуск объектов или возврат одно го и того же объекта дважды.
Самый простой способ управления изменениями базовой коллекции во времЯ обхода итератором заключается в том, чтобы после изменения базовой коллклекС ции считать итератор неправильным. Чтобы это реализовать, каждый ас коллекции должен иметь методы, которые увеличивают счетчик в случае ее из менения. Объекты-итераторы могут обнаружить изменение своей базовой кол лекции, фиксируя разные показания счетчика изменений. Если метод объек та-итератора извещается, что базовая коллекция была изменена, то он може1
сгенерировать исключение.
Iterator _ 225
Более надежный способ управления изменениями базовой коллекции во время обхода итератора заключается в том, чтобы гарантировать возврат итератором согласованного набора результатов. Для решения этой задачи применяется не сколько способов. Хотя выполнение полного копирования базовой коллекции дает хорошие результаты в большинстве случаев, это, как правило, самый не }Iжелательный подход, поскольку он требует максимальных затрат времени
памяти.
СЛЕДСТВИЯ
© Доступ к коллекции объектов возможен при отсугствии сведений об источ нике объектов.
© При использовании множества объектов-итераторов очень просто осушест влять и управлять несколькими обходами одновременно.
@) Класс коллекции может предоставлять различные объекты итерации, кото рые обходят коллекцию по-разному. Например, класс коллекции, поддер живающий ассоциацию между объектами ключей и объектами значений, может иметь одни методы создания итераторов, которые обходят только объекты ключей, и другие методы для создания итераторов, которые обхо дят только объекты значений.
ПРИМЕНЕНИЕ В JAVA API
Классы коллекций в пакете j ava . util созданы в соответствии с шаблоном
Iterator. Интерфейс j ava . util . Collection играет роль CollectionIF. Пакет
содержит ряд классов, реализующих j ava . uti l . Collection.
Интерфейс j ava . uti l . Iterator выполняет роль I teratorIF. Классы этого
l(]JaCCbI,пакета реализующие j ava . util . Collection, определяют внутренние закрьrгые которые реализуют j ava . util . I terator и играют роль итератора.
ПРИМЕР КОДА
вlЦекачествей примера кода рассмотрим некоторый скелет программы, реализую
проект, который описан в разделе «Контекст» . Листинг для интерфейса
InventoryI teratorIF:
public interface InventoryIteratorIF public boolean hasNextInventoryItem () public InventoryItem getNex InventoryItem ()
public boolean hasPrevInventoryItem() ;
//puыinterfacelcc Inv ntoryItemInventorylteratorgetPrevInventoryItem()
226 |
• Глава 7. Структурные шаблоны проектирования |
|
Приведем скелетный листинг для класса InventoryCollection. Листинг со.. держит метод iterator, используемый другими классами для получения объ. екта, необходимого для обхода содержимого объекта InventoryCol lection. Он включает в себя также закрытый класс, который инстанциируется методом iterator.
public class InventoryCollection {
public InventoryIteratorIF iterator () return new InventoryIterator () ;
} // i te rator ( )
private class InventoryIterator implements InventoryIteratorIF
public boolean hasNextInventoryItem ()
} // hasNext lnventoryl tem ( )
public InventoryItem getNextInventoryItem ()
} // getNextlnventoryl tem ( )
public boolean hasPrevInventoryItem()
} // has Prevl nventoryl tem ( )
public InventoryItem getPrevInventoryItem()
}// getPrevlnventoryl tem ( )
// class Inventoryl terator
//class InventoryCollection
ШАБЛОНbI ПРОЕКТИРОВАНИЯ, СВЯЗАННblЕ
С ШАБЛОНОМ ITERATOR
Adapter. Шаблон Iterator - специальная форма шаблона Adapter, предназна
ченная для последовательного доступа к содержимому объектов коллекции.
Factory Method.ДЛЯНекоторые классы коллекций могут использовать шабдоll
Factory Method принятия решения, итератор какого вида должен быть ИН"
станциирован.
Null Object. Пустые итераторы иногда используются для реализации шаблона
Null Object.