Объектно-ориентированное программирование
Объектно-ориентированное программирование (ООП) - это большая и расплывчатая концепция в языках программирования, которая имеет тенденцию включать в себя множество различных элементов, потому, что разные языки часто реализуют свое собственное видение этой концепции, предлагая в чем-то сходные, а в чем-то отличающиеся реализации.
Но одна из моделей, можно сказать, «выиграла» битву за звания "истинного" объектно-ориентированного подхода, хотя бы, если судить только по популярности. Это модель, используется в языке программирования Java, и она очень похожа на модель, используемую в C ++. Вот некоторые важные характеристики:
Производные типы и расширение типов: Большинство объектно-ориентированных языков позволяют пользователю добавлять новые поля в производные типы.
Заменяемость подтипов: Объекты типа, производного от базового типа, в некоторых случаях, могут использоваться вместо объектов базового типа.
Полиморфизм среды выполнения: Вызов подпрограммы, обычно называемой методом, привязанной к типу объекта, может диспечеризироваться во время выполнения программы в зависимости от конкретного типа объекта.
Инкапсуляция: Объекты могут скрывать некоторые свои данные.
Расширяемость: пользователи "извне" вашего пакета или даже всей вашей библиотеки могут создавать производные от ваших объектных типов и определять их поведение по своему.
Ада появилась еще до того, как объектно-ориентированное программирование стало таким уж популярным, как и сегодня. Некоторые механизмы и концепции из приведенного выше списка были в самой ранней версии Ада еще до того, как было добавлено то, что мы бы назвали поддержкой ООП:
Как мы видели, инкапсуляция реализована в Аде не на уровне типа, а на уровне пакета.
Заменяемость подтипов может быть реализована с использованием, ну да, подтипов, которые имеют полную и "разрешительную" (permissive) статическую модель замещаемости. Во время выполнения замена завершится неудачно, если динамические ограничения подтипа будут нарушены.
Полиморфизм времени выполнения может быть реализован с использованием записей с вариантами.
Однако в этом списке нет расширения типов, если вы не считать записи с вариантами, и расширяемости.
Редакция Ада 1995 года добавила функцию, заполняющую пробелы, которая позволила людям проще программировать, следуя объектно-ориентированной парадигме. Эта функция называется теговые типы.
Note
Примечание: В Ада можно написать программу не создав даже одного тегового типа. Если вы предпочитаете такой стиль программирования или вам в данный момент не нужны теговые типы, это нормально не использовать их, как в случае и со многими другими возможностями Ады.
Тем не менее, может оказаться, что они - наилучший способ выразить решение вашей задачи. А, раз это так, читайте дальше!
Производные типы
Прежде чем представить теговые типы, мы должны обсудить тему, которой мы уже касались, но на самом деле не углублялись в нее до сих пор:
Вы можете создать один или несколько новых типов из любого типа языка Ада. Производные типы встроены в язык.
Наследование типов полезно для обеспечения строгой типизации, поскольку система типов рассматривает эти два типа как несовместимые.
Но этим дело не ограничивается: создавая производный тип вы наследуете от него многое. Вы наследуете не только представление данных, но также можете унаследовать и поведение.
Когда вы наследуете тип, вы также наследуете так называемые примитивные операции. Примитивная операция (или просто примитив) - это подпрограмма, привязанная к типу. Ада определяет примитивы как подпрограммы, определенные в той же области, что и тип.
Attention
Обратите внимание: подпрограмма станет примитивом этого типа только в том случае, если:
Подпрограмма объявляется в той же области, что и тип и
Тип и подпрограмма объявляются в пакете.
Этот вид наследования может быть очень полезным и не ограничивается типами записей (вы можете использовать его и для дискретных типов, как в примере выше), но он лишь внешне похож на объектно-ориентированное наследование:
Записи не могут быть расширены с помощью этого механизма. Вы также не можете указать новое представление для нового типа: оно всегда будет то же, что и у базового типа. (Прим. пер: На самом деле, это не так и производные типы часто используются чтобы выполнять преобразование внутреннего представления типа.)
Нет возможности для динамической диспетчеризации или полиморфизма. Объекты имеют фиксированный, статический тип.
Есть и другие различия, но перечислять их все здесь нет смысла. Просто помните, что эту форму наследования вы можете использовать, если хотите иметь только статически унаследованое поведение, избежать дублирования кода и использования композиции, и которое вам не подходит, если вам нужны какие-либо динамические возможности, которые обычно ассоциируются с ООП.
Теговые типы
Версия языка Ада 1995 года представила теговые типы, чтобы удовлетворить потребность в едином решении, которое позволяет программировать в объектно-ориентированном стиле, аналогичном тому, что был описан в начале этой главы.
Теговые типы очень похожи на обычные записи, за исключением того, что добавлена следующая функциональность:
Типы имеют тег, хранящийся внутри каждого объекта и необходимый чтобы определить тип объекта во время выполнения.
Для примитивов может приминяться диспечеризация. Примитив тегового типа - это то, что вы бы назвали методом в Java или C++. Если у вас есть тип, производный от базового типа с переопределенным примитивом, то при вызове примитива для объекта, какой примитив вызовется будет зависить от точного типа объекта в момент исполнения.
Введены специальные правила, позволяющие теговому типу, производному от базового типа, быть статически совместимым с базовым типом.
Давайте посмотрим на наши первые объявления тегового типа:
Надклассовые типы
Чтобы сохранить согласованность языка, необходимо было ввести новую нотацию, обозначающую: "Данный объект относится к этому теговому типу или любому его потомку".
В Аде мы называем это надклассовым типом. Он используется в ООП, как только вам понадобится полиморфизм. Например, вы не можете выполнить следующие действия:
Это связано с тем, что объект типа T
имеет в точности тип T
,
независимо от того, является T
теговым или нет. То, что
программист пытается тут сказать, это «Я хочу, чтобы O3 содержал объект
типа My_Class
или любого производного от My_Class
типа».
Вот как вы это делаете:
Attention
Обратите внимание: Поскольку объект надклассового типа может быть размером с любого потомка его базового типа, то его размер заранее неизвестен. Таким образом, это неопределенный тип со всеми ожидаемыми ограничениями:
Он не может быть использован для поля/компоненты записи
Объект надклассового типа должен быть инициализирован немедленно (вы не можете ограничить такой тип каким-либо иным способом, кроме как путем его инициализации).
Операции диспетчеризации
Мы увидели, что можно переопределять операции в типах, производных от другого тегового типа. Конечной целью ООП является выполнение диспетчеризируемого вызова: когда вызоваемый примитив (метод) определяется точным типом объекта.
Но если задуматься, переменная типа My_Class
всегда содержит объект
именно данного типа. Если требуется переменная, которая может содержать
My_Class
или любой производный тип, она должна иметь тип
My_Class'Class
.
Другими словами, чтобы сделать диспетчеризируемый вызов, вы должны сначала получить объект, который может иметь либо конкретный тип, либо любой тип, производным от этого конкретного типа, а именно объект надклассового типа.
Внимание
Вы можете преобразовать объект типа Derived
в объект типа
My_Class
. В Аде это называется
преобразованием представления и полезно, например, если
вы хотите вызвать родительский метод.
В том случае, когда объект действительно преобразуется в объект
My_Class
, что включает изменение его тега. Поскольку теговые объекты всегда
передаются по ссылке, вы можете использовать этот вид преобразования
для изменения состояния объекта: изменения в преобразованном объекте
повлияют на оригинал. (Прим. пер.: Это не так, только преобразование
представление позволяет менять оригинал.)
Точечная нотация
Вы также можете вызывать примитивы теговых типов с помощью нотации, более привычной объектно-ориентированным программистам. Учитывая приведенный выше примитив Foo, вы также можете написать указанную выше программу следующим образом:
Если диспетчерезирующий параметр примитива является первым параметром, как в наших примерах, вы можете вызвать примитив, используя точечную нотацию. Все оставшиеся параметры передаются обычным образом:
Личные и лимитируемые типы с тегами
Ранее мы видели (в главе Изоляция), что типы могут быть объявлены лимитируемыми или личными. Эти методы инкапсуляции также могут применяться к теговым типам, как мы увидим в этом разделе.
Это пример личного тегового типа:
Это пример лимитируемого тегового типа:
Естественно, вы можете комбинировать как лимитируемые, так и личные типы и объявить лимитируемый личный теговый тип:
Обратите внимание, что код в процедуре Main
имеет два
оператора присваивания,
которые вызывают ошибки компиляции, потому что тип T
является
лимитируемым личным. Фактически, вы не можете:
присваивать
T1.E
непосредственно, потому что типT
является личным;присваивать
T1
вT2
, потому что типT
ограничен.
В этом случае нет различия между теговими типами и типами без тегов: эти ошибки компиляции также могут возникать и для нетеговых типов.
Надклассовые ссылочные типы
В этом разделе мы обсудим полезный шаблон для
объектно-ориентированного программирования в Аде: надклассовые ссылочные типы.
Начнем с примера, в котором мы объявляем теговый тип T
и
производный тип T_New
:
Обратите внимание, как мы используем пустые записи для типов T
и
T_New
. Хотя эти типы на самом деле не имеют каких-либо компонент,
мы по-прежнему можем использовать их для демонстрации диспетчеризации.
Также обратите внимание, что в приведенном выше примере используется
атрибут 'External_Tag
в реализации процедуры Show
для
получения строки с названием соответствующего тегового типа.
Как мы видели ранее, мы должны использовать надклассовый тип для
создания объектов, которые могут выполнять диспетчерезируемые вызовы.
Другими словами, будут диспетчеризироваться объекты типа T'Class
.
Например:
Более полезным приложением является объявление массива объектов,
для которых будет выполняться диспетчеризация. Например, мы хотели бы
объявить массив T_Arr
, перебрать в цикле этот массив и выполнить
диспетчеризацию в соответствии с фактическим типом каждого отдельного
элемента массива:
for I in T_Arr'Range loop
T_Arr (I).Show;
-- Call Show procedure according
-- to actual type of T_Arr (I)
end loop;
Однако непосредственно объявить массив с элементами T'Class
невозможно:
В самом деле, компилятор не может знать, какой тип фактически будет
использоваться для каждого элемента массива. Но, если мы используем
динамическое распределение памяти используя ссылочные типы, мы сможем
выделять объекты разных типов для отдельных элементов массива T_Arr
.
Мы делаем это с помощью надклассовых ссылочных типов, которые имеют
следующий формат:
type T_Class is access T'Class;
Мы можем переписать предыдущий пример, используя тип T_Class
.
В этом случае динамически выделяемые объекты этого типа будут
диспетчеризироваться
в соответствии с фактическим типом, используемым во время выделения.
Также давайте добавим процедуру Init
, которая не будет
переопределена для производного типа T_New
. Это адаптированный код:
В этом примере первый элемент (T_Arr (1)
) имеет тип T
, а
второй элемент - тип T_New
. При запуске примера процедура Init
типа T
вызывается для обоих элементов массива T_Arr
, в то
время, как вызов процедуры Show
выберет нужную
процедуру в соответствии с типом каждого элемента T_Arr
.