Изоляция
Одним из основных положений модульного программирования, а также объектно-ориентированного программирования, является инкапсуляция.
Инкапсуляция, вкратце, является концепцией, следуя которой разработчик программного обеспечения разделяет общедоступный интерфейс подсистемы и ее внутреннюю реализацию.
Это касается не только библиотек программного обеспечения, но и всего, где используются абстракции.
Ада несколько отличается от большинства объектно-ориентированных языков, тем что границы инкапсуляции в основном проходят по границам пакетов.
Простейшая инкапсуляция
Абстрактные типы данных
С таким высокоуровневым механизмом инкапсуляции может быть неочевидно, как скрыть детали реализации одного типа. Вот как это можно сделать в Аде:
В приведенном выше примере мы определяем тип для стека в публичной части (известной как видимый раздел спецификации пакета в Аде), но детали реализация этого типа скрыты.
Затем в личном разделе мы определяем реализацию этого типа. Мы также можем объявить там другие вспомогательные типы, которые будут использованы для описания основного публичного типа. Создание вспомогательных типов - это полезная и распространенная практика в Аде.
Несколько слов о терминологии:
То, как выглядит тип стека
Stack
в видимом разделе, называется частичным представлением типа. Это то, к чему имеют доступ клиенты.То, как выглядит тип стека
Stack
из личного раздела или тела пакета, называется полным представлением типа. Это то, к чему имеют доступ разработчики.
С точки зрения клиента (указывающего пакет в with
) важен только
видимый раздел, и личного вообще может не существовать. Это позволяет
очень легко просмотреть ту часть пакета, которая важна для нас.
-- No need to read the private part to use the package
package Stacks is
type Stack is private;
procedure Push (S : in out Stack;
Val : Integer);
procedure Pop (S : in out Stack;
Val : out Integer);
private
...
end Stacks;
А вот как будет использоваться пакет Stacks
:
-- Example of use
with Stacks; use Stacks;
procedure Test_Stack is
S : Stack;
Res : Integer;
begin
Push (S, 5);
Push (S, 7);
Pop (S, Res);
end Test_Stack;
Лимитируемые типы
В Аде конструкция лимитируемого типа позволяет вам объявить тип, для которого операции присваивания и сравнения не предоставляются автоматически.
Это нужно, например, для тех типов данных, для которых встроенная операция присваивания работает неправильно (например, когда требуется многоуровневевое копирование).
Ада позволяет вам определить операторы сравнения =
и /=
для
лимитируемых типов (или переопределить встроенные объявления для
нелимитируемых).
Ада также позволяет вам предоставить собственную реализацию присваивания
используя контролируемые типы.
Однако в некоторых случаях операция присваивания просто не имеет смысла;
примером может служить File_Type
из пакета Ada.Text_IO
, который
объявлен как лимитируемый тип, и поэтому все попытки присвоить один файл
другому будут отклонены как незаконные.
Дочерние пакеты и изоляция
Ранее мы видели (в разделе дочерние пакеты), что пакеты могут иметь дочерние пакеты. Изоляция играет важную роль в дочерних пакетах. В этом разделе обсуждаются некоторые правила касающиеся изоляции, действующие для дочерних пакетов.
Хотя личный раздел P
предназначен для инкапсуляции информации,
некоторые части дочернего пакета P.C
могут иметь доступ к этому личному
разделу P
. В таких случаях информация из личного раздела P
может
затем использоваться так, как если бы она была объявлена в видимом разделе
спецификации пакета. Говоря более конкретно, тело P.C
и личный раздел
пакета P.C
имеют доступ к личному разделу P
. Однако
видимый раздел спецификации P.C
имеет доступ только к видимому разделу
спецификации P
. В следующей таблице приводится сводная информация об
этом:
Часть дочернего пакета |
Доступ к личному разделу родительской спецификации |
---|---|
Спецификация: видимый раздел |
Нет |
Спецификация: личный раздел |
Да |
Тело |
Да |
В оставшейся части этого раздела показаны примеры того, как этот доступ к личной информации на самом деле работает для дочерних пакетов.
Давайте сначала рассмотрим пример, в котором тело дочернего пакета P.C
имеет доступ к личному разделу спецификации его родителя P
. В
предыдущем примере исходного кода мы видели, что процедуру Hello2
,
объявленную в личном разделе пакета Encapsulate
нельзя использовать в
процедуре Main
, поскольку ее там не видно. Однако это ограничение
не распространяется на некоторые части дочерних пакетов.
Фактически, тело дочернего пакета Encapsulate.Child
имеет доступ
к процедуре Hello2
и она может быть вызыванна оттуда, как вы можете
видеть в реализации процедуры Hello3
пакета Child
:
Тот же механизм применяется к типам, объявленным в личном разделе родительского пакета. Например, тело дочернего пакета может получить доступ к компонентам записи, объявленной в личном разделе его родительского пакета. Рассмотрим пример:
В этом примере у нас нет доступа к компоненте Number
типа записи
Priv_Rec
в процедуре Main
. Вы можете увидеть это в вызове
Put_Line
, который был закомментирован в реализации Main
.
Попытка получить доступ к компоненте Number
вызовет ошибку компиляции. Но у нас есть доступ к этой компоненте в
теле пакета My_Types.Ops
, поскольку это дочерний пакет пакета
My_Types
. Следовательно, тело Ops
имеет доступ к объявлению
типа Priv_Rec
, которое находится в личном разделе его родительского
пакета My_Types
. По этой причине тот же вызов Put_Line
,
который вызовет ошибку компиляции в процедуре Main
, отлично работает в
процедуре Display
пакета My_Types.Ops
.
Такой рода правила изоляции для дочерних пакетов позволяют расширять функциональность родительского пакета и в то же время обеспечивают инкапсуляцию.
Как мы упоминали ранее, в дополнение к телу пакета личный раздел
спецификации дочернего пакета P.C
также имеет доступ к личному разделу
спецификации его родителя P
. Давайте посмотрим на пример, в
котором мы объявляем объект личного типа Priv_Rec
в личном разделе
дочернего пакета My_Types.Child
и напрямую инициализируем
компоненту Number
записи Priv_Rec
:
package My_Types.Child is
private
E : Priv_Rec := (Number => 99);
end My_Types.Ops;
Естественно, мы не смогли бы инициализировать этот компонент, если бы переместили это объявление в общедоступный (видимый) раздел того же дочернего пакета:
package My_Types.Child is
E : Priv_Rec := (Number => 99);
end My_Types.Ops;
Объявление выше вызывает ошибку компиляции, поскольку тип Priv_Rec
является личным. Поскольку видимый раздел My_Types.Child
также виден за пределами дочернего пакета, Ада запрещает доступ
к личной информации в этом разделе спецификации.