Опубликован: 23.10.2005 | Доступ: свободный | Студентов: 4087 / 201 | Оценка: 4.44 / 4.19 | Длительность: 33:04:00
Специальности: Программист
Лекция 15:

OO-программирование и язык Ada

От Ada 83 к Ada 95

Версия языка Ada 95 предусматривает добавление ОО-концепций. В ней нет понятия класса в нашем смысле слова (модуль плюс тип), но есть поддержка наследования и динамического связывания для типов записей.

ОО-механизмы языка Ada 95: пример

Текст ниже приведенного пакета иллюстрирует некоторые технические приемы Ada 95. Его смысл должен быть достаточно ясен для читателя. Для получения нового типа с дополнительными полями (форма наследования Ada 95), нужно объявить уже существующий тип, такой как ACCOUNT, как дескрипторный (tagged). Это, конечно, противоречит принципу Открыт-Закрыт, поскольку необходимо знать заранее, какие типы могут иметь потомков, а какие - нет. Множественное наследование отсутствует, так что тип new можно получить только из одного типа. Обратите внимание на синтаксис получения нового типа без добавления атрибутов ( 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, а compound - частью SAVINGS_ACCOUNT, им не требуются их первые аргументы.) Здесь же все, что требуется, - так это объявление подпрограмм и типа как части одного и того же пакета; им даже не нужно находиться рядом друг с другом. В приведенном примере, только расположение показывает читателю, что определенные программы концептуально связаны с определенными дескрипторными типами записей.

Это отличается от обычного взгляда на создание ОО ПО. Хотя дескрипторный тип и связанные с ним подпрограммы с теоретической точки зрения являются частью одного абстрактного типа данных, они не образуют синтаксической единицы. Это противоречит принципу Лингвистических Модульных Единиц, предполагающему тесную связь между модульной концепцией и синтаксической структурой.

Появление нового объявления для balance в SAVINGS_ ACCOUNT сигнализирует о переопределении. Процедуры withdraw и deposit не переопределяются. Как будет понятно, это означает, что Ada 95 использует механизм перегрузки для получения ОО-эффекта от переопределения подпрограмм. Не существует синтаксической метки (как redefine ), сигнализирующей о переопределении. Чтобы увидеть, что функция balance в SAVINGS_ACCOUNT отличается от базовой версии в ACCOUNT, следует просмотреть весь текст пакета. В данном случае каждая версия подпрограммы находится рядом с соответствующим типом, с отступами для выделения этой связи, но это условность стиля, а не правило языка.

Дескрипторный тип может объявляться как abstract, соответствуя понятию отложенного класса. Подпрограмму также можно сделать abstract, не создавая для нее тело.

Функция, возвращающая результат абстрактного типа, должна сама быть абстрактной. Это правило сначала может показаться странным и помехой для написания эффективной функции, возвращающей, скажем, вершину стека фигур в предположении, что тип FIGURE абстрактный. В языке Ada, однако, результат такой функции обычно будет принадлежать не типу FIGURE, а "типу доступа", описывающему ссылки на экземпляры FIGURE. Так что можно будет написать эффективную функцию.

К сущностям дескрипторного типа можно применить динамическое связывание, как в следующем примере:

procedure print_balance (a: in ACCOUNT'Class) is
            -- Печать текущего баланса.
    begin
        Put (balance (a));
        New_Line;
     end print_balance;

Динамическое связывание следует задать явным образом. Подпрограмма объявляется как "выходящая за рамки класса" ( classwide operation ) заданием классификатора 'Class для типа аргумента. Это напоминает объявление в C++ любой динамически связываемой функции как "виртуальной". Только здесь клиент выбирает статическое или динамическое связывание.

Ada 95 позволяет определить "дочерний пакет" A1.B существующего пакета A. Это дает новому пакету возможность получить свойства из A и добавить свои собственные расширения и модификации. (Это понятие, конечно, близко к наследованию, но отличается от него.) Вместо объявления трех типов счетов в одном пакете, возможно, лучше было бы разделить пакет на три, где Accounts.Checking представляет CHECKING_ACCOUNT и его подпрограммы, а Accounts.Saving делает то же для SAVINGS_ACCOUNT.

Ada 95 и объектная технология: оценка

Если рассматривать язык Ada 95 с позиций объектной технологии, то сначала он может привести в замешательство. Со временем, освоив различные языковые механизмы, можно добиться эффекта единичного наследования, полиморфизма и динамического связывания.

Однако цена этого - сложность. К сложному языку Ada 83 добавился новый набор понятий со многими внутренними связями и связями со старыми конструкциями. При сравнении с ОО-методом, где введено достаточно простое понятие класса, обнаружится, что в Ada 95 нужно изучить, по крайней мере, пять сложных понятий:

  • пакеты, являющиеся модулями, но не типами, могут быть родовыми, предлагая нечто похожее на наследование: дочерние пакеты (как и ряд других возможностей, не описанных подробно выше, таких как возможность объявления дочернего пакета как private );
  • дескрипторные типы записей, являющиеся типами, но не модулями и имеющие некоторую форму наследования, хотя в отличие от классов они не позволяют синтаксического включения подпрограмм в объявление типа;
  • задачи, являющиеся модулями, но не типами и не имеющие наследования;
  • типы задач, являющиеся модулями и типами, но без возможности быть родовыми (хотя они могут включаться в родовые пакеты) и не имеющие наследования;
  • "защищенные типы" (понятие, до сих пор не встречавшееся), являющиеся типами и включающие подпрограммы, что делает их похожими на классы, но без наследования:
protected type ANOTHER_ACCOUNT_TYPE is
        procedure deposit (amount: in MONEY);
        function balance return MONEY;
    private
        deposit_list: ...; ...
    end ANOTHER_ACCOUNT_TYPE;

Комбинация возможностей взаимодействия поразительна. Например, пакеты имеют, в добавление к понятию дочернего пакета, механизмы Ada use и with. В одном из руководств дается следующее объяснение:

Закрытые потомки предназначены для "внутренних" пакетов, которые должны применять механизм with только к ограниченному числу пакетов. Закрытый потомок может применить механизм with только к телу своего родителя или к его потомкам. В обмен на такое ограничиние потомок получает новые полномочия: его спецификация автоматически видима в открытых и закрытых частях спецификаций всех его предков.

Без сомнения, можно уловить смысл подобных объяснений. Но стоит ли результат усилий?

Интересно отметить, что Жан Ичбиа, создатель языка Ada, публично покинул аналитическую группу Ada 95 после тщетных попыток сохранить расширения простыми. В его пространном заявлении об уходе дается следующий комментарий: дополнительные возможности приведут в результате к огромному увеличению сложности в 9X [позже Ada 95]... В 9X количество рассматриваемых взаимодействий приближается к 60000.

Базовые понятия объектной технологии, при всей их силе, удивительно просты. В языке Ada 95 предпринята, возможно, самая амбициозная попытка сделать их сложными.

Обсуждение: наследование модулей и типов

При изучении языка Ada 95 попутно интересно отметить, что разработчики Ada 95 считали необходимым помимо механизма наследования для дескрипторных типов ввести понятие пакета потомка. Язык Ada, конечно, всегда разделял понятия модуля и типа, в то время как классы объединяют эти два понятия. Но методологи языка Ada 95 предлагают при введении типа наследника, такого как SAVINGS_ACCOUNТ, объявлять его в целях ясности и модульности не в первоначальном пакете ( Accounts ), а в пакете потомка. Если обобщить этот совет, то дойдет до создания, наряду с иерархией типов, иерархии модулей, строго ему следующей.

У классов в объектной технологии такие вопросы не возникают. Классы являются модулями, и существует только одна иерархия.

Выбор, сделанный в Ada 95, является еще одним примером популярного взгляда, что " следует отделять наследование типа от повторного использования кода ". Понимание же объектной технологии, начиная с языка Simula, заключается в соединении понятий - модуля и типа, подтипов и модульного расширения. Как и любое другое смелое соединение понятий, считавшихся ранее совершенно различными, эта идея могла временами пугать, но без нее мы бы лишились замечательного упрощения архитектуры ПО.

Вперед к ОО-языку Ada

Язык Ada 95 кажется сложным. Но это не значит, что сама идея создания ОО-языка Ada обречена. Просто следует ставить реальные цели и постоянно заботиться о простоте и состоятельности. Сообщество Ada может снова попытаться разработать ОО-расширение, сопровождающееся удалением некоторых возможностей. Возможны два общие направления:

  • Первая идея, близкая по духу к замыслу Ada 95, состоит в сохранении пакетной структуры и введении понятия класса, обобщающего типы записей Ada, с поддержкой наследования и динамического связывания. Но это должны быть действительные классы, включающие применимые подпрограммы. Такое расширение, в принципе, подобно расширению, ведущему от C к C++. Оно должно стремиться к минимализму, пытаясь применять как можно шире уже существующие механизмы (такие как with и use для пакетов), не вводя новых возможностей, приводящих потом к проблемам взаимодействия, упоминаемых Ичбиа.
  • Другой подход может строиться на замечании, сделанном при представлении задач в данной лекции. Отмечалось, что типы задач близки по духу к классам, поскольку они могут иметь экземпляры, созданные во время выполнения. Структурно они обладают многими свойствами пакетов. Можно было бы ввести модуль, имеющий, грубо говоря, синтаксис пакетов и семантику классов. Можно думать о нем как о пакет-классе, или о типе задач, необязательно являющихся параллельными. Понятие "защищенного типа" может стать отправной точкой, будучи интегрировано в существующий механизм.

Упражнения в конце данной лекции предлагают исследовать эти возможности.