display is
-- Отображает фигуру, последовательно отображая все ее компоненты.
do
from
start
until
after
loop
item.display
forth
end
end
Как и в предыдущих рассмотрениях, мы предполагаем, что класс список предлагает механизм обхода элементов, основанный на понятии курсора. Команда start устанавливает курсор на первый элемент, если он есть (иначе after сразу же равно True), after указывает, обошел ли курсор все элементы, item дает значение элемента, на который указывает курсор, forth передвигает курсор к следующему элементу. |
Я нахожу эту схему прекрасной и, надеюсь, вы тоже пленитесь ее красотой. В ней вы найдете почти весь арсенал средств: классы, множественное наследование, полиморфные структуры данных (LINKED_LIST [FIGURE]), динамическое связывание (вызов item.display применяет метод display того класса, которому принадлежит текущий элемент списка), рекурсию (каждый элемент item сам может быть составной фигурой без ограничения глубины вложенности). Подумать только: есть люди, которые могут прожить всю жизнь и не увидеть этого великолепия!
Но можно пойти еще дальше. Обратимся к другим компонентам COMPOSITE_FIGURE - методам вращения (rotate) и переноса (translate). Они также должны выполнять надлежащие операции над каждым элементом фигуры, и каждый из них может во многом напоминать display. Для ОО-проектировщика это может стать причиной тревоги: хотелось бы избежать повторения; потому выполним преобразование - от инкапсуляции к повторному использованию. (Это могло бы стать девизом.) Техника, рассматриваемая здесь, состоит в использовании отложенного класса "итератор", чьи экземпляры способны выполнять цикл по COMPOSITE_FIGURE. Его эффективным потомком может стать DISPLAY_ ITERATOR, а также ряд других классов. Реализацию этой схемы мы оставляем читателю (см. упражнение 15.4).
Описание составных структур с применением множественного наследования и списка или иного контейнерного класса, как одного из родителей, - это универсальный образец проектирования. Примерами его воплощения являются подменю (см. упражнение 15.8), а также составные команды в ряде интерактивных систем.
Брак по расчету
В приведенных примерах оба родителя играли симметричные роли, но это не всегда так. Иногда вклад каждого из них различен по своей природе.
Важным приложением множественного наследования является обеспечение реализации абстракции, описанной отложенным классом, используя свойства, обеспечиваемые эффективным классом. Один класс абстрактен, второй - эффективен.
Рис. 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. Так, условие, при котором стек полон, имеет вид:
fulclass="underline" 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. Это - инвариант реализации.