В родовом классе C [G, H, ...] рассмотрим сущность, чей тип - один из формальных родовых параметров, например x типа G. Когда класс используется клиентом для объявления сущностей, G, разумеется, может представлять любой тип. Поэтому любая операция, которую выполняют подпрограммы C над x, должна быть применима ко всем типам. Это ограничение позволяет выполнять только пять видов операций:
Использование сущностей формального родового типа
Корректно использовать сущность x, чей тип задан формальным родовым параметром G, можно следующим образом.
1 Слева от оператора присваивания x := y, где выражение y также имеет тип G.
2 Справа от оператора присваивания y := x, где сущность y также типа G.
3 В логических выражениях вида x = y или x /= y, где y также типа G.
4 Как фактический аргумент в вызове подпрограммы на месте формальных параметров типа G, или типа ANY.
5 Как цель вызова компонента класса ANY.
В частности, инструкция создания вида create x неприменима, так как нам ничего неизвестно о процедурах создания, если таковые есть, для класса, определенного возможным фактическим родовым параметром, соответствующим G.
Случаи (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