Абстрактный класс (abstract class) - это класс, который нельзя реализовать непосредственно. Вместо этого создается экземпляр подкласса. Обычно абстрактный класс имеет одну или более абстрактных операций. У абстрактной операции (abstract operation) нет реализации; это чистое объявление, которое клиенты могут привязать к абстрактному классу.
Наиболее распространенным способом обозначения абстрактного класса или операции в языке UML является написание их имен курсивом. Можно также сделать свойства абстрактными, определяя абстрактное свойство или методы доступа. Курсив сложно изобразить на доске, поэтому можно прибегнуть к метке: {abstract}.
Интерфейс - это класс, не имеющий реализации, то есть вся его функциональность абстрактна. Интерфейсы прямо соответствуют интерфейсам в С# и Java и являются общей идиомой в других типизированных языках. Интерфейс обозначается ключевым словом «interface».
Классы обладают двумя типами отношений с интерфейсами: предоставление или требование. Класс предоставляет интерфейс, если его можно заменить на интерфейс. В Java и.NET класс может сделать это, реализуя интерфейс или подтип интерфейса. В C++ создается подкласс класса, являющегося интерфейсом.
|
|
Класс требует интерфейс, если для работы ему нужен экземпляр данного интерфейса. По сути дела, это зависимость от интерфейса.
На рис. 5.6 эти отношения демонстрируются в действии на базе небольшого набора классов, заимствованных из Java. Я мог бы написать класс Order (Заказ), содержащий список позиций заказа (Line Items). Поскольку я использую список, то класс Order зависит от интерфейса List (Список). Предположим, что он вызывает методы equals, add и get. При выполнении связывания объект Order действительно будет использовать экземпляр класса ArrayList, но ему не нужно знать, что необходимо вызывать эти три метода, поскольку они входят в состав интерфейса List.
Класс ArrayList - это подкласс класса AbstractList. Класс AbstractList предоставляет некоторую, но не всю реализацию поведения интерфейса List. В частности, метод get - абстрактный. В результате ArrayList реализует метод get, а также переопределяет некоторые другие операции класса AbstractList. В данном случае он переопределяет метод add, но вполне удовлетворен наследованием реализации метода equals
Почему бы мне просто не отказаться от этого и не заставить Order прямо использовать ArrayList? Применение интерфейса позволяет мне получить преимущество при последующем изменении реализации, если потребуется. Другой способ реализации может оказаться более производительным - он может предоставить функции работы с базой данных или другие возможности. Программируя интерфейс, а не реализацию, я избегаю необходимости переделывать весь код, когда достаточно изменить реализацию класса List. Следует всегда стараться программировать интерфейс так, как показано выше, то есть всегда использовать наиболее общий тип.
|
|
Относительно вышесказанного приведу один практический совет. Когда программисты применяют коллекцию, подобную приведенной здесь, они обычно инициализируют ее при объявлении, например:
private List lineltems = new ArrayListO;
Обратите внимание, что это определенно приводит к зависимости Order от конкретного ArrayList. С точки зрения теории это проблема, но на практике разработчиков это не беспокоит. Поскольку lineltems объявлен как List, то никакая другая часть класса Order не зависит от Array-List. При необходимости изменить реализацию нужно побеспокоиться лишь об одной строке кода инициализации. Общепринято ссылаться на конкретный класс единожды - при создании, а впоследствии использовать только интерфейс.
Полная нотация на рис. 5.6 - это один из способов обозначения интерфейса. На рис. 5.7 показана более компактная нотация. Тот факт, что ArrayList реализует List и Collection, показан с помощью кружков, называемых часто «леденцами на палочках*. То, что Order требует интерфейс List, показано с помощью значка «гнездо». Связь совершенно очевидна,
В UML уже применялась нотация «леденцов на палочках», но гнездовая нотация - это новинка UML 2. (Мне кажется, это моя любимая нотация из добавленных.) Возможно, вы встретите более старые диаграммы, использующие стиль, представленный на рис. 5.8, где зависимость основана на нотации леденцов.
Любой класс - это сочетание интерфейса и реализации. Поэтому мы i часто можем видеть, что объект используется посредством интерфейса одного из его суперклассов. Определенно, было бы допустимо исполь-
зовать для суперкласса нотацию леденцов, поскольку суперкласс - это класс, а не чистый интерфейс. Но я обхожу эти правила для ясности.
Разработчики сочли, что нотация леденцов полезна не только для диаграмм классов, но и в других местах. Одна из вечных проблем диаграмм взаимодействий заключается в том, что они не обеспечивают хорошую визуализацию полиморфного поведения. Хотя это нормативное применение, вы можете обозначить такое поведение вдоль линий, как на рис. 5.9. Здесь, как вы можете видеть, хотя у нас есть экземпляр класса Salesman, который используется объектом Bonus Calculator как таковой, но объект Pay Period использует Salesman только через его интерфейс Employee. (Тот же самый прием может применяться и в случае коммуникационных диаграмм.)