Вот набросок класса SKIER:
class SKIER feature
roommate: SKIER
-- Сосед по номеру.
share (other: SKIER) is
-- Выбрать в качестве соседа other.
require
other /= Void
do
roommate := other
end
... Другие возможные компоненты, опущенные в этом и последующих классах ...
end
Нас интересуют два компонента: атрибут roommate и процедура share, "размещающая" данного лыжника в одном номере с текущим лыжником:
s1, s2: SKIER
...
s1.share (s2)
При объявлении сущности other можно отказаться от типа SKIER в пользу закрепленного типа like roommate (или like Current для roommate и other одновременно). Но давайте забудем на время о закреплении типов (мы к ним еще вернемся) и посмотрим на проблему ковариантности в ее изначальном виде.
Как ввести переопределение типов? Правила требуют раздельного проживания юношей и девушек, призеров и остальных участников. Для решения этой задачи при переопределении изменим тип компонента roommate, как показано ниже (здесь и далее переопределенные элементы подчеркнуты).
class GIRL inherit
SKIER
redefine roommate end
feature
roommate: GIRL
-- Сосед по номеру.
end
Переопределим, соответственно, и аргумент процедуры share. Более полный вариант класса теперь выглядит так:
class GIRL inherit
SKIER
redefine roommate, share end
feature
roommate: GIRL
-- Сосед по номеру.
share (other: GIRL) is
-- Выбрать в качестве соседа other.
require
other /= Void
do
roommate := other
end
end
Аналогично следует изменить все порожденные от SKIER классы (закрепление типов мы сейчас не используем). В итоге имеем иерархию:
Рис. 17.5. Иерархия участников и повторные определения
Так как наследование является специализацией, то правила типов требуют, чтобы при переопределении результата компонента, в данном случае roommate, новый тип был потомком исходного. То же касается и переопределения типа аргумента other подпрограммы share. Эта стратегия, как мы знаем, именуется ковариантностью, где приставка "ко" указывает на совместное изменение типов параметра и результата. Противоположная стратегия называется контравариантностью.
Все наши примеры убедительно свидетельствуют о практической необходимости ковариантности.
[x]. Элемент односвязного списка LINKABLE должен быть связан с другим подобным себе элементом, а экземпляр BI_LINKABLE - с подобным себе. Ковариантно потребуется переопределяется и аргумент в put_right.
[x]. Всякая подпрограмма в составе LINKED_LIST с аргументом типа LINKABLE при переходе к TWO_WAY_LIST потребует аргумента BI_LINKABLE.
[x]. Процедура set_alternate принимает DEVICE-аргумент в классе DEVICE и PRINTER-аргумент - в классе PRINTER.
Ковариантное переопределение получило особое распространение потому, что скрытие информации ведет к созданию процедур вида
set_attrib (v: SOME_TYPE) is
-- Установить attrib в v.
...
для работы с attrib типа SOME_TYPE. Подобные процедуры, естественно, ковариантны, поскольку любой класс, который меняет тип атрибута, должен соответственно переопределять и аргумент set_attrib. Хотя представленные примеры укладываются в одну схему, но ковариантность распространена значительно шире. Подумайте, например, о процедуре или функции, выполняющей конкатенацию односвязных списков (LINKED_LIST). Ее аргумент должен быть переопределен как двусвязный список (TWO_ WAY_LIST). Универсальная операция сложения infix "+" принимает NUMERIC-аргумент в классе NUMERIC, REAL - в классе REAL и INTEGER - в классе INTEGER. В параллельных иерархиях телефонной службы процедуре start в классе PHONE_SERVICE может требоваться аргумент ADDRESS, представляющий адрес абонента, (для выписки счета), в то время как этой же процедуре в классе CORPORATE_SERVICE потребуется аргумент типа CORPORATE_ADDRESS.
Рис. 17.6. Службы связи
Что можно сказать о контравариантном решении? В примере с лыжниками оно означало бы, что если, переходя к классу RANKED_GIRL, тип результата roommate переопределили как RANKED_GIRL, то в силу контравариантности тип аргумента share можно переопределить на тип GIRL или SKIER. Единственный тип, который не допустим при контравариантном решении, - это RANKED_GIRL! Достаточно, чтобы возбудить наихудшие подозрения у родителей девушек.
Параллельные иерархии
Чтобы не оставить камня на камне, рассмотрим вариант примера SKIER с двумя параллельными иерархиями. Это позволит нам смоделировать ситуацию, уже встречавшуюся на практике: TWO_ WAY_LIST > LINKED_LIST и BI_LINKABLE > LINKABLE; или иерархию с телефонной службой PHONE_SERVICE.
Пусть есть иерархия с классом ROOM, потомком которого является GIRL_ROOM (класс BOY опущен):
Рис. 17.7. Лыжники и комнаты
Наши классы лыжников в этой параллельной иерархии вместо roommate и share будут иметь аналогичные компоненты accommodation (размещение) и accommodate (разместить):
indexing
description: "Новый вариант с параллельными иерархиями"
class SKIER1 feature
accommodation: ROOM