Интерфейсы и абстрактные классы

Абстрактный класс (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. (Тот же самый прием может применяться и в слу­чае коммуникационных диаграмм.)



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: