Брак по расчету
Брак по расчету
В приведенных примерах оба родителя играли симметричные роли, но это не всегда так. Иногда вклад каждого из них различен по своей природе.
Важным приложением множественного наследования является обеспечение реализации абстракции, описанной отложенным классом, используя свойства, обеспечиваемые эффективным классом. Один класс абстрактен, второй - эффективен.
Рис. 15.11. Брак по расчету
Рассмотрим реализацию стека, заданную массивом. У нас уже есть классы для поддержки стеков и массивов в отдельности (абстрактный STACK и эффективный ARRAY, см. предыдущие лекции). Лучший способ реализации класса ARRAYED_STACK (стек, заданный массивом) - описать его как наследника классов STACK и ARRAY. Это концептуально верно: стек-массив одновременно является стеком (с точки зрения клиента) и массивом (с позиций поставщика). Вот описание класса:
indexing
description: "Стек, реализованный массивом"
class ARRAYED_STACK [G] inherit
STACK [G]
ARRAY [G]
... Здесь будут добавлены предложения переименования ...
feature
...Реализация отложенных подпрограмм класса STACK
в терминах операций класса ARRAY (см. ниже)...
end
ARRAYED_STACK предлагает ту же функциональность, что и STACK, делая эффективными отложенные компоненты: full, put, count ..., реализуя их как операции над массивом.
Вот схема некоторых типичных компонентов: full, count и put. Так, условие, при котором стек полон, имеет вид:
full: BOOLEAN is
-- Является ли стек (его представление) заполненным?
do
Result := (count = capacity)
end
Компонент capacity унаследован от класса ARRAY и задает емкость стека, равную числу элементов массива. Для count потребуется ввести атрибут:
count: INTEGER
Это пример эффективной реализации отложенного компонента как атрибута. Наконец,
put (x: G) is
-- Втолкнуть x на вершину.
require
not full
do
count := count + 1
array_put (x, count)
end
Процедура array_put унаследована от класса ARRAY. Ее цель - записать новое значение в указанный элемент массива.
Компоненты capacity и array_put имели в классе ARRAY имена count и put. Смену прежних имен мы поясним позднее.Класс ARRAYED_STACK типичен как вариант наследования, образно именуемый "брак по расчету". Оба класса, - абстрактный и эффективный, - дополняя друг друга, создают достойную пару.
Помимо эффективной реализации методов, отложенных (deferred) в классе STACK, класс ARRAYED_STACK способен переопределять реализованные. Компонент change_top, реализованный в STACK в виде последовательности вызовов remove и put, можно переписать более эффективно:
array_put (x, count)
Указание на переопределение компонента следует ввести в предложение наследования:
class ARRAYED_STACK [G] inherit
STACK [G]
redefine change_top end
... Остальное, как прежде ...
Инвариант этого класса может иметь вид
invariant
non_negative_count: count >= 0
bounded: count <= capacity
Первое утверждение выражает свойство АТД. Фактически оно присутствует в родительском классе STACK и потому является избыточным. Здесь оно приводится в педагогических целях. Из окончательной версии класса его нужно изъять. Второе утверждение включает емкость массива - capacity. Это - инвариант реализации.
Сравнив ARRAYED_STACK с представленным ранее классом STACK2, вы увидите, как сильно он упростился благодаря наследованию. Это сравнение мы продолжим при обсуждении методологии наследования, в ходе которого ответим на критику, звучащую иногда в адрес наследования "по расчету" и так называемого наследования реализаций.