Хотя инкапсулирующих языков много, относительно широко используются всего несколько из них. Пять языков заслуживают особого внимания. Язык
Создание языка
Помня об успехе языка
Язык
Дальнейший пересмотр языка
Был ли язык
По иронии судьбы разработчики языка
Уроки языка
Любой инкапсулирующий язык предлагает модульную конструкцию для группирования логически связанных программных элементов. В языке
Класс определяется и как структурный системный компонент - модуль, и как тип. Напротив, пакет - это только модуль. Ранее отмечалось, что пакеты являются чисто синтаксическими понятиями, а классы имеют и семантическое значение. Пакеты дают способ распределения элементов системы (переменных, подпрограмм ...) в согласованные подсистемы, но они нужны только для управляемости и
Пакет языка
Последнее использование наиболее интересно для данного обсуждения. Оно будет изучаться на примере пакета, описывающего стеки, взятого из руководства по языку
Скрытие информации поддерживается в языке
Интерфейс перечисляет общедоступные свойства пакета: экспортированные переменные, константы, типы и подпрограммы. Для подпрограмм он дает только заголовки, перечисляя
function item (s: STACK) return X;
Часть, содержащая тело пакета, обеспечивает реализацию подпрограмм и добавляет любые необходимые секретные элементы.
Первую версию интерфейса пакета, задающего стек, можно выразить следующим образом. Заметим, что ключевое слово package (пакет) вводит интерфейс; тело, появляющееся позднее, вводится сочетанием package body (тело пакета).
package REAL_STACKS is
type STACK_CONTENTS is array (POSITIVE range <>) of FLOAT;
type STACK (capacity: POSITIVE) is
record
implementation: STACK_CONTENTS (1..capacity);
count: NATURAL := 0;
end record;
procedure put (x: in FLOAT; s: in out STACK);
procedure remove (s: in out STACK);
function item (s: STACK) return FLOAT;
function empty (s: STACK) return BOOLEAN;
Overflow, Underflow: EXCEPTION;
end REAL_STACKS;
Этот интерфейс перечисляет экспортированные элементы: тип STACK - для объявления стеков, вспомогательный тип STACK_CONTENTS, используемый типом STACK, четыре открытые подпрограммы (процедуры и функции) и два исключения. Клиентские пакеты будут опираться только на интерфейс (предполагается, что создающие их программисты имеют представление о семантике, связанной с программами).
Этот пример наводит на несколько общих замечаний:
STACK и STACK_CONTENTS, появившихся в том, что должно быть чистым интерфейсом. Кратко рассмотрим причину этой проблемы и способ ее устранения.STACK следует определить отдельно. Одним из следствий этого отделения для программиста, создающего пакет вокруг реализации абстрактного типа данных, является необходимость изобретения двух различных имен - одно для пакета, другое - для типа. Другое следствие состоит в том, что подпрограммы имеют еще один аргумент по сравнению со своими ОО-аналогами: здесь все они имеют первым аргументом стек s, в то время как для класса он задается неявно (см. предыдущие лекции).count в типе STACK предписывает исходное значение 0. Оно устраняет необходимость явной операции инициализации, задаваемой процедурой создания (конструктором) класса. Однако этот способ не работает, если требуется более сложная инициализация.POSITIVE и NATURAL обозначают INTEGER, включающие, соответственно, положительные и неотрицательные целые, TYPE range <> ), где <> известно как Box-символ, описывает шаблон для TYPE. Здесь это делается при определении типа STACK, использующем интервал [1..capacity ] типа POSITIVE. STACK является примером параметризованного типа. Любое объявление сущности типа STACK должно задавать фактическое значение емкости стека capacity , как в:s: STACK (1000)
in, out или in out, определяющим права подпрограммы на использование in.Overflow и Underflow . Исключение - это ситуация, когда из-за ошибок прерывается нормальный порядок вычислений. Интерфейс пакета должен перечислить любые исключения, которые могут возбуждаться в процессе работы подпрограмм пакета и передаваться для обработки клиентам. Подробно механизм исключений языка Приведем пример из клиентского пакета, использующего стек вещественных чисел:
s: REAL_STACKS.STACK (1000); REAL_STACKS.put (3.5, s); ...; if REAL_STACKS.empty (s) then ...;
Среда языка REAL_STACKS, не имея доступа к его телу.
Синтаксически каждое использование сущности (здесь "сущности" включают имена программ и типов) повторяет REAL_STACKS. Это утомительно - необходима неявная форма квалификации. Если включена директива:
use REAL_STACKS;
в начале клиентского пакета, то выражения записываются проще:
s: STACK (1000); put (3.5, s); ...; if empty (s) then ...;
Конечно, используется и полная форма для сущности, чье имя вступает в конфликт с именем, указанным в другом доступном пакете (скажем, объявленное в самом пакете или в пакете из списка в директиве use).
В литературе по языку use, поскольку она мешает ясности: неквалифицированная ссылка, например вызов empty (s), сразу не говорит о поставщике empty (в нашем примере REAL_STACKS ). Его аналог в ОО-подходе, s.empty, однозначно определяет поставщика через цель s.
В ОО-мире подобная проблема возникает из-за наследования: имя в классе может ссылаться на компонент, объявленный любым из предков. Техника, частично решающая проблему, - это плоская форма класса.
Тело пакета REAL_STACKS может объявляться следующим образом. Полностью показана только одна подпрограмма.
package body REAL_STACKS is
procedure put (x: in FLOAT; s: in out REAL_STACK) is
begin
if s.count = s.capacity then
raise Overflow
end if;
s.count := s.count + 1;
s.implementation (count) := x;
end put;
procedure remove (s: in out STACK) is
... Реализация remove ...
end remove;
function item (s: STACK) return X is
... Реализация item ...
end item;
function empty (s: STACK) return BOOLEAN is
... Реализация empty ...
end empty;
end REAL_STACKS;
Два свойства, показанные в этом примере, будут подробно обсуждаться ниже: использование исключений и необходимость повторения в теле большей части информации интерфейса (заголовков подпрограммы).
Пакет, в том виде как он появился, слишком специфичен. Он приложим к типу FLOAT, а хотелось бы задания произвольного типа. Чтобы сделать его универсальным, в языке
generic
type G is private;
package STACKS is
... Все, как и ранее, заменяя все вхождения FLOAT на G ...
end STACKS;
Предложение generic синтаксически более тяжелое, чем наша ОО-нотация для class C [G]...), но зато в нем больше возможностей. В частности, параметры, объявляемые в generic, могут представлять не только типы, но и подпрограммы. В приложении B эти возможности обсуждаются при сравнении универсальности и наследования.
В теле пакета generic не повторяется, там достаточно конкретный тип FLOAT заменить родовым G.
Спецификация is private заставляет остальную часть пакета рассматривать G как закрытый тип. Это означает, что сущности этого типа могут использоваться только в операциях, применимых ко всем типам языка limited private, что запрещает все использования кроме
Называясь пакетом, универсально параметризованный модуль, такой как STACKS, в действительности является шаблоном пакета, поскольку клиенты не могут использовать его непосредственно; они должны получить из него действительный пакет, используя фактические родовые параметры. Новую версию нашего пакета стеков действительных величин можно определить через следующее родовое порождение:
package REAL_STACKS_1 is new STACKS (FLOAT);
Родовое порождение - главный механизм языка
Пакет STACKS в том виде, как он задан, не реализует принцип скрытия информации. Объявления типов STACK и STACK_CONTENTS, находясь в интерфейсе, позволяют клиентам непосредственный доступ к представлению стеков. Например, клиент может включить код вида:
[1]
use REAL_STACKS_1;...
s: STACK; ...
s.implementation (3) := 7.0; s.last := 51;
грубо нарушая основную спецификацию абстрактных типов данных.
Концептуально объявления типа должны находиться в теле. Почему их туда не помещают с самого начала? Объяснение находится вне языка и требует рассмотрения проблем программного окружения.
Одно из уже упомянутых требований к языку
Если есть доступ к интерфейсу REAL_STACKS_1 (то есть к интерфейсу STACKS, REAL_STACKS_1 является просто его родовым порождением), можно компилировать любого из его клиентов. Такой клиент будет содержать объявления вида:
use REAL_STACKS_1;... s1, s2: STACK; ... s2 := s1;
Компилятор не сможет их хорошо обрабатывать, не зная размера объекта типа STACK. Но это может определяться только из объявлений типа для STACK и вспомогательного типа STACK_CONTENTS.
Отсюда концептуальная дилемма, стоявшая перед проектировщиками языка
Пришлось создать чистилище: специальный раздел пакета, физически видимый в интерфейсе и компилируемый с ним, но такой, что клиенты не могут обращаться к его элементам. Чистилище - это закрытая часть интерфейса, она вводится ключевым словом private. Любое объявление, появляющееся здесь, недоступно клиентам. Эта схема иллюстрируется нашей последней версией интерфейса пакета, задающего стек:
generic
type G is private;
package STACKS is
type STACK (capacity: POSITIVE) is private;
procedure put (x: in G; s: in out STACK);
procedure remove (s: in out STACK);
function item (s: STACK) return G;
function empty (s: STACK) return BOOLEAN;
Overflow, Underflow: EXCEPTION;
private
type STACK_VALUES is array (POSITIVE range <>) of G;
type STACK (capacity: POSITIVE) is
record
implementation: STACK_VALUES (1..capacity);
count: NATURAL := 0;
end record
end STACKS;
Отметим, тип STACK теперь должен объявляться дважды: сначала в открытой части интерфейса, где он специфицируется как private, затем еще раз в закрытой части, где дается полное описание. Без первого объявления строка вида s: REAL_STACK не будет разрешенной в клиенте, поскольку доступ есть только к сущностям, объявляемым в открытой части. Первое объявление, специфицируя тип как private, запрещает клиентам доступ к любым свойствам помимо универсальных операций: присваивания, проверки на равенство и использование в качестве
Заметьте, тип STACK_VALUES чисто внутренний и не нужен клиентам. Поэтому он не объявляется в открытой части интерфейса пакета.
Важно понять, что информация, помещаемая в закрытую часть интерфейса, должна была быть в теле пакета и появляется в STACKS клиентский код, выше помеченный как [1], имевший прямой доступ к представлению в клиенте, становится неправильным.
Авторы клиентских модулей могут видеть внутреннюю структуру экземпляров STACK, но они не могут воспользоваться ею в своих модулях. Это могло бы приводить разработчиков к танталовым мукам. (Хорошая среда языка short, описанный в предыдущих лекциях.) Удивительная для новичков, эта политика не противоречит правилу скрытия информации. Как отмечалось ранее, цель скрытия не в том, чтобы не дать авторам клиента возможности прочитать скрытые подробности, а чтобы не дать им использовать эти подробности.
Те, кому хотелось бы все усложнить, могли бы подвести итог в двух предложениях (произнесенных очень быстро, чтобы произвести впечатление и на друзей, и на врагов):. Закрытый раздел интерфейса пакета перечисляет реализацию тех концептуально закрытых типов, которые должны быть объявлены в интерфейсе, хотя их реализация недоступна для использования. В открытой части интерфейса эти типы объявлены закрытыми.
Пакет STACKS определяет два исключения в своем интерфейсе: и . Язык
Некоторые элементы механизма
Исключения в языке
action1;
if error1 then
error_handling1;
else
action2;
if error2 then
error_handling2;
else
action3;
if error3 then
error_handling3;
else
...
Механизм исключений в
Чтобы возбудить
action1; if error1 then raise exc1; end; action2; if error2 then raise exc2; end; action3; if error3 then raise exc3; end; ...
При выполнении команды raise exc нормальный порядок вычислений прерывается, и управление передается обработчику исключений (exception handler), представленному специальным блоком подпрограммы и имеющему вид:
exception
when exc1, ...=> treatment1;
when exc2 ...=> treatment2;
...
При возбуждении исключения exc первым его обрабатывает захвативший его обработчик из динамической цепи вызовов - списка элементов, начинающегося подпрограммой, содержащей вызвавшее исключение предложение raise, и всеми вызывающими подпрограммами, как показано на рис. 15.1:
(рис 15.1) Цепь вызовов (этот рисунок впервые появился в лекции 12 курса "Основы объектно-ориентированного программирования")Говорят, что обработчик захватывает exc, если exc появляется в одном из его предложений when (или он содержит предложение вида when others ). Такой обработчик выполняет соответствующие команды (после символа => ), после чего управление передается вызывающей программе или заканчивается в случае главной программы. (exc, выполнение приложения заканчивается, и управление возвращается к операционной системе, а она, вероятно, выведет системное сообщение об ошибке.
Интересно сравнить механизм
К техническим различиям можно отнести способы задания исключений. В одном случае используются множественные предложения when, в другом - наследование от класса EXCEPTIONS. Более важно включение в объектную нотацию возможности повторной попытки, что потребовало введения специального ключевого слова . Язык или подобных управляющих структур.
Методологическое различие вытекает из принятой строгой политики, ведущей к принципу Дисциплинированной
Стоит повторить основное правило:
Правило исключений языка Ada Выполнение любого обработчика исключений языка |
Исключения в
Запись raise some_exception дает впечатление освобождения от запутанной и скучной задачи STACKS типичны. Попытка поместить элемент в полный стек вызывает ошибку , а попытка доступа к пустому стеку вызывает . Как обрабатывать , ошибку, возникающую при вызове remove или item на пустом стеке? Обсуждение Проектирования по Контракту показало, что эти подпрограммы не могут знать, что следует делать в такой ситуации. Вся ответственность лежит на клиенте, вызвавшем эти подпрограммы, только он может решить, что следует делать. У него и должен содержаться код вида:
[2]
use REAL_STACKS;
procedure proc (...) is
s: STACK; ...
begin
... remove (s); ...
exception
when Underflow => action1;
...
end proc;
Клиент должен точно определить, что происходит в случае ошибки. Опустить оператор when было бы ошибкой проекта. Сравните это с обычной, не основанной на исключении, формой вызова:
[3]
if not s.empty then s.remove else action1 end
(или вариантом, определяющим ошибку апостериори). Форма [2], использующая исключения, отличается от формы [3] только двумя аспектами:
action1 текстуально отделен от вызова, приведшего к ошибке;Хотя и желательно избегать глубоко вложенных структур обработки ошибок if... then... else..., приведенных в начале лекции, то место в алгоритме, где обнаруживается ошибка, часто предоставляет наилучшую информацию для ее обработки. Если разделить обнаружение и обработку, то могут потребоваться сложные управляющие структуры для случаев, требующих повторного запуска или продолжения обработки.
Кроме того, для подпрограммы, содержащей несколько вызовов remove, способ работы с пустыми стеками вряд ли будет одним и тем же в каждом случае.
Существуют два общих стиля использования исключений. Стиль управляющей структуры рассматривает исключения как нормальный механизм для обработки всех случаев, отличающихся от обычных. Стиль аварийных случаев рассматривает их как непредсказуемые ситуации, когда все другие механизмы не работают. Объектный подход rescue/retry, описанный ранее, тяготеет к стилю аварийных случаев, хотя может использоваться и для первого стиля. Обработка исключений в языке
Каждый должен решить, какой стиль ему больше нравится. Но в любом случае, следует помнить, что не нужно наивно возлагать надежды на использование исключений. Есть механизм исключений или его нет, ошибки при выполнении программы - это факт жизни системы, и она должна их явно обрабатывать. Хороший методологический подход, поддерживаемый
Кроме пакетов,
Синтаксически, задачи имеют много общего с пакетами. Главное различие в том, что задача - это не просто единица модульности, но и представление процесса, выполняемого параллельно с другими процессами. Поэтому подобно классу (и в отличие от пакета) задача является как синтаксической так и семантической конструкцией.
Как и пакет, задача имеет интерфейс и тело. Вместо подпрограмм, спецификация задачи вводит несколько входов ( ). Для клиента входы выглядят как процедуры, например, интерфейс задачи, управляющей буфером, может выглядеть так:
task BUFFER_MANAGER is
entry read (x: out G);
entry write (x: in G);
end BUFFER_MANAGER;
(Задачи не могут быть универсальными, так что тип G должен быть глобально доступным, или родовым параметром охватывающего пакета.) Только реализация входов отличает их от процедур: команды accept, появляющиеся в теле, будут специфицировать синхронизацию и другие ограничения на выполнение записей. Здесь, например, можно предписать, чтобы только одно read или write действовало в любой момент времени, чтобы read ожидало, пока буфер не станет непустым, а write - пока он не будет неполным.
Кроме отдельных задач, можно специфицировать тип задачи и использовать его для создания стольких задач - экземпляров accept с различными условиями для эмуляции
Версия языка
Текст ниже приведенного пакета иллюстрирует некоторые технические приемы ACCOUNT, как дескрипторный (tagged). Это, конечно, противоречит принципу Открыт-Закрыт, поскольку необходимо знать заранее, какие типы могут иметь потомков, а какие - нет. null record, к удивлению, без end ).
package Accounts is
type MONEY is digits 12 delta 0.01;
type ACCOUNT is tagged private;
procedure deposit (a: in out ACCOUNT; amount: in MONEY);
procedure withdraw (a: in out ACCOUNT; amount: in MONEY);
function balance (a: in ACCOUNT) return MONEY;
type CHECKING_ACCOUNT is new ACCOUNT with private;
function balance (a: in CHECKING_ACCOUNT) return MONEY;
type SAVINGS_ACCOUNT is new ACCOUNT with private;
procedure compound (a: in out SAVINGS_ACCOUNT; period: in Positive);
private
type ACCOUNT is tagged
record
initial_balance: MONEY := 0.0;
owner: String (1..30);
end record;
type CHECKING_ACCOUNT is new ACCOUNT with null record;
type SAVINGS_ACCOUNT is new ACCOUNT with
record
rate: Float;
end record;
end Accounts;
Дескрипторные типы по-прежнему объявляются как записи. Основное свойство большинства ОО-языков - операции над типом являются частью типа и фактически определяют тип - здесь не работает. Подпрограммы задаются вне объявления типа и принимают в качестве аргумента значение типа. (В ОО-языках, deposit и т. д. будут частью объявления ACCOUNT, а - частью SAVINGS_ACCOUNT, им не требуются их первые аргументы.) Здесь же все, что требуется, - так это объявление подпрограмм и типа как части одного и того же пакета; им даже не нужно находиться рядом друг с другом. В приведенном примере, только расположение показывает читателю, что определенные программы концептуально связаны с определенными дескрипторными
Это отличается от обычного взгляда на создание ОО ПО. Хотя дескрипторный тип и связанные с ним подпрограммы с теоретической точки зрения являются частью одного абстрактного типа данных, они не образуют синтаксической единицы. Это противоречит принципу Лингвистических Модульных Единиц, предполагающему тесную связь между модульной концепцией и синтаксической структурой.
Появление нового объявления для balance в SAVINGS_ ACCOUNT сигнализирует о переопределении. Процедуры withdraw и deposit не переопределяются. Как будет понятно, это означает, что ), сигнализирующей о переопределении. Чтобы увидеть, что функция balance в SAVINGS_ACCOUNT отличается от базовой версии в ACCOUNT, следует просмотреть весь текст пакета. В данном случае каждая версия подпрограммы находится рядом с соответствующим типом, с отступами для выделения этой связи, но это условность стиля, а не правило языка.
Дескрипторный тип может объявляться как , соответствуя понятию отложенного класса. Подпрограмму также можно сделать , не создавая для нее тело.
Функция, возвращающая результат абстрактного типа, должна сама быть абстрактной. Это правило сначала может показаться странным и помехой для написания абстрактный. В языке , а "типу доступа", описывающему ссылки на экземпляры . Так что можно будет написать эффективную функцию.
К сущностям дескрипторного типа можно применить динамическое связывание, как в следующем примере:
procedure print_balance (a: in ACCOUNT'Class) is
-- Печать текущего баланса.
begin
Put (balance (a));
New_Line;
end print_balance;
Динамическое связывание следует задать явным образом. Подпрограмма объявляется как "выходящая за рамки класса" ( classwide operation ) заданием
Accounts.Checking представляет CHECKING_ACCOUNT и его подпрограммы, а Accounts.Saving делает то же для SAVINGS_ACCOUNT.
Если рассматривать язык
Однако цена этого - сложность. К сложному языку
private );protected type ANOTHER_ACCOUNT_TYPE is
procedure deposit (amount: in MONEY);
function balance return MONEY;
private
deposit_list: ...; ...
end ANOTHER_ACCOUNT_TYPE;
Комбинация возможностей взаимодействия поразительна. Например, пакеты имеют, в добавление к понятию дочернего пакета, механизмы use и with. В одном из руководств дается следующее объяснение:
Закрытые потомки предназначены для "внутренних" пакетов, которые должны применять механизм with только к ограниченному числу пакетов. Закрытый потомок может применить механизм with только к телу своего родителя или к его потомкам. В обмен на такое ограничиние потомок получает новые полномочия: его спецификация автоматически видима в открытых и закрытых частях спецификаций всех его предков.
Без сомнения, можно уловить смысл подобных объяснений. Но стоит ли результат усилий?
Интересно отметить, что Жан Ичбиа, создатель языка |
Базовые понятия объектной технологии, при всей их силе, удивительно просты. В языке
При изучении языка SAVINGS_ACCOUNТ, объявлять его в целях ясности и модульности не в первоначальном пакете ( Accounts ), а в пакете потомка. Если обобщить этот совет, то дойдет до создания, наряду с иерархией типов, иерархии модулей, строго ему следующей.
У классов в объектной технологии такие вопросы не возникают. Классы являются модулями, и существует только одна иерархия.
Выбор, сделанный в
Язык
with и use для пакетов), не вводя новых возможностей, приводящих потом к проблемам взаимодействия, упоминаемых Ичбиа.Упражнения в конце данной лекции предлагают исследовать эти возможности.
[Booch 1986a] обсуждает (под маркой "ОО-проектирование", но не используя классы, полиморфизм и т. д.) как достичь некоторых преимуществ объектной ориентации в
Официальное описание языка
Ссылки на другие модульные языки, упомянутые в начале этой лекции: [Mitcell 1979] - Mesa, [Wirth 1982] -
Комментированный список учебников по
Проблема компиляции пакетов
Родовые параметры пакетов
Перепишите класс COMPLEX как
(Это упражнение предполагает хорошее знание языка
(Это упражнение предполагает хорошее знание
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.