Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP / books / Osnovi objektno-orientirovannogo programmirovaniya.pdf
Скачиваний:
62
Добавлен:
03.03.2016
Размер:
9.04 Mб
Скачать

реализация и начинается новая жизнь.

Отложенные классы

Как мы видели, компонент может быть отложенным или эффективным. То же относится и к классам.

Определение: отложенный класс, эффективный класс

Класс является отложенным, если у него имеется отложенный компонент. В противном случае, класс является эффективным.

Таким образом, чтобы класс был эффективным, должны быть эффективными все его компоненты. Один или несколько отложенных компонентов делают класс отложенным. В этом случае класс должен содержать специальную метку:

Правило объявления отложенного класса

Объявление отложенного класса должно включать подряд идущие ключевые слова deferred class (в отличие от одного слова class для эффективных классов).

Поэтому класс FIGURE будет объявлен следующим образом:

deferred class

FIGURE

feature

rotate (...) is

отложенных компонентов ...

... Объявления

... Объявления

других

компонентов ...

end

 

 

Обратно, если класс отмечен как отложенный, то у него должен быть хотя бы один отложенный компонент. При этом класс может быть отложенным, даже если в нем самом не объявлен ни один отложенный компонент, так как у него может быть отложенный родитель, от которого он унаследовал отложенный компонент, не ставший у него эффективным. В нашем примере в классе OPEN_FIGURE, скорее всего, останутся отложенными компоненты display, rotate и многие другие, унаследованные от класса FIGURE, поскольку понятие незамкнутой фигуры не настолько конкретизировано, чтобы поддерживать стандартные реализации этих операций. Поэтому этот класс является отложенным и будет объявлен как

deferred class OPEN_FIGURE inherit FIGURE

...

даже если в нем самом не вводится ни один отложенный компонент.

Потомок отложенного класса является эффективным классом, если все отложенные компоненты его родителей имеют в нем эффективные определения и в нем не вводятся никакие собственные отложенные компоненты. Эффективные классы, такие как POLYGON и ELLIPSE, должны обеспечить реализацию отложенных компонентов display, rotate.

Для удобства мы будем называть тип отложенным, если его базовый класс является отложенным. Таким образом, класс FIGURE, рассматриваемый как тип, является отложенным. Если родовой класс LIST является отложенным (как это и должно быть, если он представляет понятие списка, не зависящее от реализации), то тип LIST [INTEGER] является отложенным. Учитывается только базовый класс: C [X] будет эффективным, если класс C эффективный, и отложенным, если C является отложенным, независимо от статуса X.

Соглашения о графических обозначениях

Сейчас можно полностью объяснить графические символы, использованные на рис. 14.8. Звездочкой отмечаются отложенные компоненты или классы:

FIGURE*

display*

perimeter* -- На уровне класса OPEN_FIGURE на рис. 14.8

Знак плюс означает "эффективный" и им отмечается эффективизация компонента:

perimeter+ -- На уровне POLYGON на рис. 14.8

Чтобы указать, что класс эффективный, можно отметить его знаком +. По умолчанию, неотмеченный класс считается эффективным, так же как в текстовом виде объявление class C без ключевого слова deferred означает, что класс эффективный.

Можно присоединять одиночный плюс к компоненту для указания того, что он стал эффективным. Например, компонент perimeter появляется как отложенный и, следовательно, имеет вид perimeter* в классе CLOSED_FIGURE. Затем на уровне POLYGON для этого компонента дается реализация и он отмечается в этом классе как perimeter+.

Наконец, два знака плюс отмечают переопределение:

perimeter++ -- На уровне RECTANGLE и SQUARE на рис.14.8

Что делать с отложенными классами?

Присутствие отложенных элементов в системе вызывает вопрос: "что случится, если компонент rotate применить к объекту типа FIGURE?" или в общем виде - "можно ли применить отложенный компонент к прямому экземпляру отложенного класса?" Ответ может обескуражить: такой вещи как объект типа FIGURE не существует - прямых экземпляров отложенных классов не бывает.

Правило отсутствия экземпляров отложенных классов

Тип создания в процедуре создания не может быть отложенным.

Напомним, что тип создания - это тип x, для формы create x, и U для формы create {U} x. Тип считается отложенным, если таков его базовый класс.

Поэтому вызов конструктора create f некорректен и будет отвергнут компилятором, если типом f будет один из отложенных классов: FIGURE, OPEN_FIGURE, CLOSED_FIGURE. Это правило устраняет опасность ошибочных вызовов компонентов.

|Отметим однако, что даже, если тип сущности f отложенный, то допустима явная форма процедуры создания - create{RECTANGLE} f, поскольку здесь типом создания является эффективный потомок FIGURE - класс RECTANGLE. Мы уже видели, как этот прием используется в многовариантной процедуре создания для объектов класса FIGURE, которые, в зависимости от контекста, будут экземплярами эффективных классов RECTANGLE, CIRCLE и др. |

Может показаться, что это правило ограничивает полезность отложенных классов, делая их просто синтаксической уловкой для обмана системы статических типов. Это было бы верно, если бы не полиморфизм и динамическое связывание. Нельзя создать объект типа FIGURE, но можно объявить полиморфную сущность этого типа, а затем использовать ее, не зная точно, к объекту какого типа она присоединена в конкретном вычислении:

f: FIGURE

...

f := "Некоторое выражение эффективного типа, такого как CIRCLE или POLYGON"

...

f.rotate (some_point, some_angle)

f.display

...

Такие примеры являются комбинацией и кульминацией уникальных средств абстракции ОО-метода таких, как классы, скрытие информации, единственный выбор, наследование, полиморфизм, динамическое связывание, отложенные классы (и, как будет видно дальше, утверждения). Вы манипулируете объектами, не зная точно их типов, задавая только минимум информации, необходимой для требуемых операций. Имея надежный штамп контролера типов, удостоверяющий согласованность вызовов этих операций с их объявлениями, можно рассчитывать на большую силу - динамическое связывание, которая позволяет применять корректную версию каждой операции, не зная точно, что это за версия.

Задание семантики отложенных компонентов и классов

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

Как и другие классы, отложенный класс может иметь инвариант, а у отложенного компонента может быть предусловие, постусловие или оба эти утверждения.

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

Рис. 14.9. Список с курсором Этот класс является отложенным:

indexing

description: "Линейные списки" deferred class

LIST [G]

feature -- Access count: INTEGER is

--Число элементов deferred

end

index: INTEGER is

--Положение курсора deferred

end

item: G is

--Элемент в позиции курсора deferred

end

feature - Отчет о статусе after: BOOLEAN is

--Курсор за последним элементом? deferred

end

before: BOOLEAN is

--Курсор перед первым элементом? deferred

end

feature - Сдвиг курсора forth is

--Передвинуть курсор на одну позицию вперед. require

not after deferred ensure

index = old index + 1 end

... Другие компоненты ...

invariant

non_negative_count: count >= 0 offleft_by_at_most_one: index >= 0 offright_by_at_most_one: index <= count + 1 after_definition: after = (index = count + 1) before_definition: before = (index = 0)

end

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

Рис. 14.10. Позиции курсора

|Два последних предложения инварианта можно также представить в виде постусловий:

ensure Result = (index = count + 1) для after и ensure Result = (index = 0) для before. Такой выбор всегда возникает при выражении свойств, включающих только запросы без аргументов. Я предпочитаю использовать предложения инварианта, рассматривая такие свойства как глобальные свойства класса, а не прикреплять их к конкретному компоненту. |

Утверждения о forth точно выражают то, что должна делать эта процедура: передвигать курсор на одну позицию. Поскольку курсор должен оставаться в пределах списка элементов плюс две позиции "меток" слева и справа, то применение forth требует выполнения условия not after, а результатом будет, как сказано в постусловии, увеличение index на один.

Вот другой пример - наш старый друг стек. Нашей библиотеке потребуется общий класс STACK [G], который будет отложенным, так как он должен покрывать всевозможные реализации. Его собственные потомки, такие как FIXED_STACK и LINKED_STACK, будут описывать конкретные реализации. Одной из отложенных процедур класса STACK является put:

put (x: G) is

-- Поместить x на вершину. require

not full deferred ensure

not_empty: not empty pushed_is_top: item = x one_more: count = old count + 1 end

Булевские функции empty и full (также отложенные на уровне STACK ) выражают свойство стека быть пустым и заполненным.

Только с помощью утверждений отложенные классы достигают своей полной силы. Как уже отмечалось (хотя детали появятся через две лекции), предусловия и постусловия применимы ко всем переопределениям процедуры. Это особенно важно в отложенном случае: в нем такие утверждения будут ограничивать все допустимые реализации. Таким образом, приведенная спецификация ограничивает все варианты put в потомках класса STACK.

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

В конце этой лекции мы вновь обратимся к отложенным классам и исследуем глубже их роль в процессе ОО-анализа, проектирования и реализации.

Соседние файлы в папке books