Выбрать главу

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. Это - инвариант реализации.