Чтение онлайн

ЖАНРЫ

Основы объектно-ориентированного программирования

Мейер Бертран

Шрифт:

По соглашению родовой параметр обычно, использует имя G (от Generic). Это неформальное правило. Если нужны еще родовые параметры, они будут названы H, I и т.д.

Согласно синтаксису, формальные родовые параметры заключаются в квадратные скобки, следующие за именем класса, подобно синтаксису параметризованного АТД в предыдущей лекции. Например:

indexing

description: "Стек элементов произвольного класса G"

class STACK [G] feature

count: INTEGER

– - Количество элементов в стеке

empty: BOOLEAN is

– - Есть ли элементы?

do ... end

full: BOOLEAN is

– - Стек заполнен?

do ... end

item: G is

– - Вершина стека

do ... end

put (x: G) is

– - Втолкнуть x в стек.

do ... end

remove is

– - Вытолкнуть элемент из стека.

do ... end

end -- class STACK

Формальный родовой параметр G можно использовать в объявлениях класса не только для результата функций (как в item) и формальных аргументов подпрограмм (как в put), но и для атрибутов и локальных сущностей класса.

Использование родового класса

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

sp: STACK [POINT]

Если у класса несколько родовых параметров, то соответственно столько же необходимо задать и фактических параметров.

Предоставление фактических родовых параметров родовому классу для создания типа называется родовым порождением (generic derivation), а полученный в результате класс, такой как STACK [POINT], называют параметрически порожденным классом.

Родовому порождению требуется тип, родовое порождение создает новый тип:

[x]. Результат порождения STACK [POINT] является типом.

[x]. Для получения такого результата, необходим уже существующий тип, используемый в качестве фактического параметра (POINT в примере).

Фактический параметр может быть произвольным типом. Ничто не мешает выбрать тип, который сам по себе параметрически порожден. Предположим, что мы определили другой родовой класс LIST [G], тогда можно определить стек, элементы которого являются списками точек:

slp: STACK [LIST [POINT]]

или, используя STACK [POINT] как фактический родовой параметр, - стек стеков точек:

ssp: STACK [STACK [POINT]]

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

Терминология

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

[x]. Процесс порождения нового типа, такого как STACK [POINT], из типов POINT и STACK, можно было бы называть созданием экземпляра типа "generic instantiation". Но этот термин мог бы ввести в заблуждение, поскольку в названии неявно предполагается процесс периода выполнения ПО. Заметьте, родовое порождение - статический механизм, действующий на текст программы, а не на ее выполнение.

[x]. В этой книге термин "параметр" и "аргумент" используются по-разному. Первый для универсальных классов, второй - для подпрограмм. В традиционной программистской терминологии параметры и аргументы чаще всего синонимы.

Проверка типов

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

sc: STACK [CIRCLE]; sa: STACK [ACCOUNT]; c: CIRCLE; a: ACCOUNT.

Тогда в программах этого класса допустимы следующие инструкции:

sc.put (c) -- Втолкнуть круг в стек кругов

sa.put (a) -- Втолкнуть счет в стек счетов

c := sc.item -- Сущности круг присвоить вершину стека кругов.

Но каждая из следующих инструкций недопустима и будет отвергнута:

sc.put (a); -- Попытка: Втолкнуть счет в стек кругов.

sa.put (c); -- Попытка: Втолкнуть круг в стек счетов.

c:= sa.item -- Попытка: Дать кругу значение счета.

Это исключает ошибочные операции, подобные попытке вычитания денег из круга.

Правило типизации

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

Вначале рассмотрим обычные, не родовые классы. Пусть C такой класс. Рассмотрим объявление его компонента, не использующее, естественно, никаких формальных родовых параметров:

f(a:T):U is ...

Тогда вызов вида x.f(d), появляющийся в произвольном классе B, где x типа C, будет корректен по типу, тогда и только тогда, когда:

[x]. f доступен классу B, - экспортирован всем классам или множеству классов, включающих B;

[x]. d принадлежит типу T. Если учитывать возможность наследования, то d может принадлежать потомкам T.

[x]. Результат вызова имеет тип U. В этом примере предполагается, что компонент f является функцией.

Поделиться с друзьями: