Опубликован: 17.10.2005 | Уровень: специалист | Доступ: свободно
Лекция 16:

Техника наследования

Типизация и повторное объявление

Повторное объявление компонентов не требует сохранения сигнатуры. Пока оно виделось нам как замена одного алгоритма другим или - для отложенного компонента - запись алгоритма, соответствующего ранее заданной спецификации.

Но, воплощая идею о том, что класс способен предложить более специализированную версию элемента, описанного его предком, мы вынуждены иногда изменять типы данных. Приведем два характерных примера.

Устройства и принтеры

Вот простой пример переопределения типа. Рассмотрим понятие устройства, включив предположение о том, что для любого устройства есть альтернатива, так что устройство можно заменить, если оно по каким-либо причинам недоступно:

class DEVICE feature
         alternate: DEVICE
         set_alternate (a: DEVICE) is
                         -- Пусть a - альтернативное устройство.
                do
                         alternate := a
                end
       ... Прочие компоненты ...
end

Принтер является устройством, так что использование наследования оправдано. Но альтернативой принтера может быть только принтер, но не дисковод для компакт-дисков или сетевая карта, - поэтому мы должны переопределить тип:

Устройства и принтеры

Рис. 16.6. Устройства и принтеры
class PRINTER inherit
         DEVICE
                  redefine alternate, set_alternate
feature
         alternate: PRINTER
         set_alternate (a: PRINTER) is
                           -- Пусть a - альтернативное устройство.
                  ... Тело как у класса DEVICE ...
         ... Прочие компоненты ...
end

В этом и проявляется специализирующая природа наследования.

Одно- и двусвязные элементы

В следующем примере мы обратимся к базовым структурам данных. Рассмотрим библиотечный класс LINKABLE, описывающий односвязные элементы, используемые в LINKED_LIST - одной из реализаций списков. Вот частичное описание класса:

indexing
          description: "Односвязные элементы списка"
class LINKABLE [G] feature
          item: G
          right: LINKABLE [G]
          put_right (other: LINKABLE [G]) is
                            -- Поместить other справа от текущего элемента.
                   do right := other end
          ... Прочие компоненты ...
end
Односвязный элемент списка

Рис. 16.7. Односвязный элемент списка

Ряд приложений требуют двунаправленных списков. Класс TWO_WAY_LIST - наследник LINKED_LIST должен быть также наследником класса BI_LINKABLE, являющегося наследником класса LINKABLE.

Параллельные иерархии

Рис. 16.8. Параллельные иерархии

Двусвязный элемент списка имеет еще одно поле:

Двусвязный элемент списка

Рис. 16.9. Двусвязный элемент списка

В состав двунаправленных списков должны входить лишь двусвязные элементы (хотя последние, в силу полиморфизма, вполне можно внедрять и в однонаправленные структуры). Переопределив right и put_right, мы гарантируем однородность двусвязных списков.

indexing
         description: "Элементы двусвязного списка"
class BI_LINKABLE [G] inherit
         LINKABLE [G]
                  redefine right, put_right end
feature
         left, right: BI_LINKABLE [G]
         put_right (other: BI_LINKABLE [G]) is
                           -- Поместить other справа от текущего элемента.
do
                           right := other
                           if other /= Void then other.put_left (Current) end
                  end
         put_left (other: BI_LINKABLE [G]) is
                           -- Поместить other слева от текущего элемента.
                  ... Упражнение для читателя ...
         ... Прочие компоненты ...
invariant
         right = Void or else right.left = Current
         left = Void or else left.right = Current
end

(Попробуйте написать put_left. Здесь скрыта ловушка! См. приложение A.)

Правило повторного объявления типов

Примеры, рассмотренные выше, несмотря на все их различия, объединяет необходимость повторного объявления типов. Спуск по иерархии наследования означает специализацию классов, и в соответствии со специализацией изменяются типы функций и типы аргументов подпрограмм, как, например, a в set_alternate и other в put_right ; изменяются типы запросов - alternate и right.

Этот аспект повторного объявления выражает следующее правило:

Правило повторного объявления типов

При повторном объявлении компонента можно заменить тип компонента (для атрибутов и функций) или тип формального параметра (для подпрограмм) любым совместимым типом.

Правило использует понятие совместимости типов. Связка "или", стоящая в тексте правила, не исключает того, что при повторном объявлении функции мы можем одновременно изменить как тип результата функции, так и тип одного или нескольких ее аргументов.

Любое повторное объявление ведет к специализации, а, следовательно, к изменению типов. Так, с переходом к двунаправленным спискам параметры и результаты функций сменили свой тип на BI_LINKABLE. Отсюда становится понятен тот термин, которым часто описывают политику редекларации типов, - ковариантная типизация (covariant typing), где приставка "ко" указывает на параллельное изменение типов при спуске по диаграмме наследования.

Ковариантная типизация таит в себе немало проблем, которые возникают у создателей компиляторов, нередко перекладывающих их решение на плечи разработчиков приложений.

Александр Шалухо
Александр Шалухо
Как сбросить прогресс по курсу? Хочу начать заново
Анатолий Садков
Анатолий Садков
Вопросик
Александр Качанов
Александр Качанов
Япония, Токио
Янош Орос
Янош Орос
Украина, Киев