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

Случаи (4) и (5) ссылаются на класс ANY. Упомянутый уже несколько раз, этот класс содержит компоненты, наследуемые всеми классами. Поэтому можно быть уверенным, что независимо от фактического типа G при родовом порождении компоненты будут доступны. Компонентами класса ANY являются все основные операции копирования и сравнения объектов: clone, copy, equal, deep_clone, deep_equal и др. Поэтому для x и y формального родового типа G корректно использовать следующие инструкции:

x.copy (y)

x := clone (y)

if equal (x, y) then ...

Случай (4) разрешает вызов a.f(x) в родовом классе C [G], если f имеет формальный аргумент типа G. В частности, возможна ситуация, когда a может быть типа D [G], где D другой родовой класс. В классе D [G] объявлен компонент f, требующий аргумент типа G, обозначающий в этом случае формальный родовой параметр класса D, а не класса С. (Если предыдущая фраза не совсем понятна, перечитайте ее еще раз, и, надеюсь, она покажется столь же прозрачной10.2), как горный ручей.)

Типы и классы

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

С появлением универсализации второе утверждение перестало быть буквально истинным, хотя нюанс невелик. Родовой класс, объявленный как C [G], является не типом, а шаблоном типа, задающим бесконечное множество возможных типов. Любой тип из этого множества можно получить, предоставив фактический родовой параметр, который, в свою очередь, является типом.

Это приводит к более общему и гибкому понятию. Но за выигрыш в мощности приходится немного пожертвовать простотой: только при небольшом насилии над языком можно продолжать говорить о "компонентах класса T" или о "клиентах T", если x объявлен, как имеющий тип T. Теперь T может быть параметрически порожденным типом C [U] из некоторого родового класса C и некоторого типа U. Конечно, основой типа остается родовой класс C, поэтому насилие над языком приемлемо.

Если требовать буквальной строгости, то терминология следующая. Любой тип T ассоциируется с базовым классом T, поэтому всегда можно говорить о компонентах и клиентах базового класса T. Если T неродовой класс, то он же является и базовым классом. Если T родовое порождение C [U, ...], то C является базовым классом T.

Базовые классы будут использоваться при введении еще одного вида типов, основанного также (как и все остальное в ОО-подходе) на классе, но косвенно: закрепленного типа (см. гл. 16.7).

Массивы

В заключение этой дискуссии полезно рассмотреть пример контейнерного класса ARRAY, представляющего одномерный массив.

Массивы как объекты

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

ARRAY хороший пример родового класса. Рассмотрим первый набросок этого класса:10.3)

indexing

description: "Последовательность значений одного типа или согласуемых типов,%

%доступных через целые индексы в заданном диапазоне"

class ARRAY [G] creation

make

feature

make (minindex, maxindex: INTEGER) is

-- Размещение массива с границами minindex и maxindex

-- (пустой, если minindex > maxindex)

do ... end

lower, upper, count: INTEGER

-- Минимальный и максимальный допустимый индекс; размер массива.

put (v: G; i: INTEGER) is

-- Присвоить v элементу массива с индексом i

do ... end

infix "@", item (i: INTEGER): G is

-- Элемент с индексом i

do ... end

end -- класса ARRAY

Для создания массива a с границами m и n, тип объявления которого ARRAY [T] с заданным типом T, нужно выполнить инструкцию создания

create a.make (m, n)

Для задания значений элементов массива используется процедура put: вызов a.put(x, i) присваивает значение x i-ому элементу. Для доступа к элементам можно использовать функцию item (синоним инфиксной операции @, поясняемой позже), например:

x := a.item (i)

Вот схема того, как этот класс может быть использован клиентом:

pa: ARRAY [POINT]; p1: POINT; i, j: INTEGER

...

create pa.make (-32, 101) -- Разместить массив с указанными границами.

pa.put (p1, i) -- Присвоить значение p1 элементу с индексом i.

...

p1 := pa.item (j) -- Присвоить сущности p1 значение элемента с индексом j.

В обычной нотации (скажем, в Pascal) нужно писать:

pa [i] := p1 вместо pa.put (p1, i)

p1 := pa [i] вместо p1 := pa.item (i)

Свойства массива

Некоторые замечания о классе.

[x]. Подобные классы существуют для массивов большей размерности: ARRAY2 и т. д.

[x]. Компонент Count может быть реализован и как атрибут и как функция, поскольку count = upper - lower+1. В реальном классе это выражается инвариантом, как объясняется в следующей лекции.

[x]. Техника утверждений позволяет связывать точные условия согласования с put и item, отражая тот факт, что вызовы допустимы, только если индекс i лежит между lower и upper.

вернуться

прозрачнойпрозрачной 10.2

вернуться

Рассмотрим первый набросок этого класса:Рассмотрим первый набросок этого класса: 10.3