К. Арнольд, Д. Гослинг - Язык программирования Java (1160779), страница 54
Текст из файла (страница 54)
Пусть интерфейсы PokerDeck и TarotDeckвключают константы DECK_SIZE с различными значениями, а интерфейс или класс MultiDeck может реализовать оба этих интерфейса. Однаковнутри Multi Deck и его подтипов вы должны пользоваться уточненными именами Poker Deck.DECK_SIZE и TarotDeck.DECK_SIZE, посколькупростое DECK_SIZE было бы двусмысленным.4.4. Реализация интерфейсовИнтерфейс описывает контракт в абстрактной форме, однако он представляет интерес лишь после того, как будет реализован в некоторомклассе.Некоторые интерфейсы являются чисто абстрактными — у них нет никакого полезного универсального воплощения, и они должны зановореализовываться для каждого нового класса. Тем не менее большая часть интерфейсов может иметь несколько полезных реализаций.
В случаенашего интерфейса Attributed можно придумать несколько возможных реализаций, в которых используются различные стратегии для хранениянабора атрибутов.Одна стратегия может быть простой и быстродействующей (если набор содержит малое количество атрибутов); другую можно оптимизироватьдля работы с наборами редко изменяемых атрибутов; наконец, третья может предназначаться для часто меняющихся атрибутов. Если бысуществовал пакет с возможными реализациями интерфейса Attributed, то класс, реализующий этот интерфейс, мог бы воспользоваться одной изних или же предоставить свой собственный вариант.В качестве примера рассмотрим простую реализацию Attributed, в которой используется вспомогательный класс java.util.Hashtable.
Позднееэто будет использовано, чтобы реализовать интерфейс Attributed для конкретного набора объектов, наделяемых атрибутами. Прежде всего,класс AttributedImpl выглядит следующим образом:import java.util.*;class AttributedImpl implements Attributed{protected Hashtable attrTable = new Hashtable();public void add(Attr newAttr) {attrTable.put(newAttr.nemeOf(), newAttr);}public Attr find(String name) {return (Attr)attrTable.get(name);}public Attr remove(String name) {return (Attr)attrTable.remove(name);}public Enumeration attrs() {return attrTable.elements();}}В реализации методов AttributedImpl используется класс Hashtable.При инициализации attrTable создается объект Hashtable, в котором хранятся атрибуты. Большая часть работы выполняется именно классомHashtable.
Класс HashTable использует метод hashCode данного объекта для хеширования. Нам не приходится писать свой метод хеширования,поскольку String уже содержит подходящую реализацию hashCode.При добавлении нового атрибута объект Attr сохраняется в хеш-таблице, причем имя атрибута используется в качестве ключа хеширования;затем по имени атрибута можно осуществлять поиск и удаление атрибутов из хеш-таблицы.Метод attrs возвращает значение Enumeration, в котором приведены все атрибуты, входящие в набор.
Enumeration является абстрактнымклассом, определенным в java.util и используемым классами-коллекциями типа Hash table для возвращения списков (см. раздел “ИнтерфейсEnumeration”). Мы также воспользуемся этим типом, поскольку он предоставляет стандартное средство для возвращения списков в Java.Фактически интерфейс Attributed определяет тип-коллекцию, поэтому применим обычный в таких случаях механизм возврата содержимогоколлекции, а именно класс Enumeration. Использование Enumeration имеет ряд преимуществ: стандартные классы-коллекции вроде Hashtable, вкоторых применяется Enumeration, позволяют упростить реализацию Attributed.4.5. Использование реализации интерфейсаЧтобы использовать класс (скажем, AttributedImpl), реализующий некоторый интерфейс, вы можете просто расширить класс. В тех случаях, когдатакой подход возможен, он оказывается самым простым, поскольку при нем наследуются все методы вместе с их реализацией.
Однако, если вамприходится поддерживать сразу несколько интерфейсов или расширять какой-то другой класс, не исключено, что придется поступить иначе.Чаще всего программист создает объект реализующего класса и перенаправляет в него все вызовы методов интерфейса, возвращая нужныезначения.Вот как выглядит реализация интерфейса Attributed, в которой объект AttributedImpl используется для наделения атрибутами нашего классанебесных тел:import java.util.Enumeration;class AttributedBody extends Bodyimplements Attributed{AttributedImpl attrImpl = new AtrributedImpl();AttributedBody() {super();}AttributedBody(String name, Body orbits) {super(name, orbits);}// Перенаправить все вызовы Attributed в объект attrImplpublic{public{public{public{}void add(Attr newAttr)attrImpl.add(newAttr); }Attr find(String name)return attrImpl.find(name); }Attr remove(String name)return attrImpl.remove(name); }Enumeration attrs()return attrImpl.attrs(); }Объявление, в соответствии с которым AttributedBody расширяет класс Body и реализует интерфейс Attributed, определяет контракт AttributedBody.
Реализация всех методов Body наследуется от самого класса Body. Реализация каждого из методов Attributed заключается вперенаправлении вызова в эквивалентный метод объекта AttributedImpl и возврате полученного от него значения (если оно имеется). Отсюдаследует, что вам придется включить в класс поле типа AttributedImpl, используемое при перенаправлении вызовов, и инициализировать егоссылкой на объект AttributedImpl.Перенаправление работает без особых хитростей и требует существенно меньших усилий, чем реализация Attributed “с нуля”.
Кроме того, если вбудущем появится более удачная реализация Attributed, вы сможете быстро перейти на нее.4.6. Для чего применяются интерфейсыМежду интерфейсами и абстрактными классами существует два важных отличия:●●Интерфейсы предоставляют некоторую разновидность множественного наследования, поскольку класс может реализовать несколькоинтерфейсов. С другой стороны, класс расширяет всего один класс, а не два или более, даже если все они состоят только из абстрактныхметодов.Абстрактный класс может содержать частичную реализацию, защищенные компоненты, статические методы и т. д., тогда как интерфейсограничивается открытыми методами, для которых не задается реализация, и константами.Эти отличия обычно определяют выбор средства, которое должно применяться для конкретного случая.
Если вам необходимо воспользоватьсямножественным наследованием, применяются интерфейсы. Однако при работе с абстрактным классом вместо перенаправления методов можночастично или полностью задать их реализацию, чтобы облегчить наследование. Перенаправление — довольно скучная процедура, к тому жечреватая ошибками, так что вам стоит лишний раз подумать, прежде чем отказываться от абстрактных методов.Тем не менее любой сколько-нибудь важный класс (абстрактный или нет), предназначенный для расширения, должен представлять собойреализацию интерфейса.
Хотя для этого потребуется немного дополнительных усилий, перед вами открывается целый спектр новыхвозможностей, недоступных в других случаях. Например, если бы мы просто создали класс Attributed вместо интерфейса Attributed,реализованного в специальном классе AttributedImpl, то тогда на его основе стало бы невозможно построить другие полезные классы типаAttributedBody — ведь расширять можно всего один класс. Поскольку Attributed является интерфейсом, у программистов появляется выбор: онимогут либо непосредственно расширить AttributedImpl и избежать перенаправления, либо, если расширение невозможно, по крайней меревоспользоваться перенаправлением для реализации интерфейса. Если общая реализация окажется неверной, они напишут свою собственную.Вы даже можете предоставить несколько реализаций интерфейса для разных категорий пользователей.
Независимо от того, какую стратегиюреализации выберет программист, создаваемые объекты будут Attributed.Упражнение 4.1Перепишите свое решение упражнения 3.7 с использованием интерфейса, если вы не сделали этого ранее.Упражнение 4.2Перепишите свое решение упражнения 3.12 с использованием интерфейса, если вы не сделали этого ранее.Упражнение 4.3Должен ли класс LinkedList из предыдущих упражнений представлять собой интерфейс? Прежде чем отвечать на этот вопрос, перепишите его сиспользованием реализующего класса.Упражнение 4.4Спроектируйте иерархию классов-коллекций с применением одних интерфейсов.Упражнение 4.5Подумайте над тем, как лучше представить следующие типы (в виде интерфейсов, абстрактных или обычных классов): 1) TreeNode — дляпредставления узлов N-арного дерева; 2) TreeWalker — для перебора узлов дерева в порядке, определяемом пользователем (например, переборв глубину или в ширину); 3) Drawable — для представления объектов, которые могут быть нарисованы в графической системе; 4) Application —для программ, которые могут запускаться с графической рабочей поверхности (desktop).Упражнение 4.6Как надо изменить условия в упражнении 4.5, чтобы ваши ответы тоже изменились?Содержание | ДалееГлава 2КЛАССЫ И ОБЪЕКТЫНачнем с самого начала, хотя порядок может быть и другим.Доктор Who, MeglosФундаментальной единицей программирования в Java является класс.
В состав классов входят методы — фрагменты выполняемого кода, вкоторых происходят все вычисления. Классы также определяют структуру объектов и обеспечивают механизмы для их создания на основеопределения класса. Вы можете ограничиваться в своих программах одними лишь примитивными типами (целыми, вещественными и т. д.), нопрактически любая нетривиальная программа на Java создает объекты и работает с ними.В объектно-ориентированном программировании между тем, что делается, и тем, как это делается, существуют четкие различия. “Что”описывается в виде набора методов (а иногда и общедоступных данных) и связанной с ними семантики. Такое сочетание (методы, данные исемантика) часто называется контрактом между разработчиком класса и программистом, использующим этот класс, так как оно определяет, чтопроисходит при вызове конкретных методов объекта.Обычно считается, что объявленные в классе методы составляют все содержание его контракта.