Rationale for Ada 2005: Introduction
RUSTOPBACKNEXT
ENG |
3.1 The object oriented model
@ The Ada 95 object oriented model has been criticized as not following the true spirit of the OO paradigm in that the notation for applying subprograms to objects is still dominated by the subprogram and not by the object concerned. @ It is claimed that real OO people always give the object first and then the method (subprogram). Thus given
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale for Ada 2005: Introduction
ENGRUSTOPBACKNEXT3.1 Объектно-ориентированная модель
@ Объектно-ориентированная модель ada критиковалась как не соответствующая истинному духу парадигмы OOП т.к. для того чтобы применить подпрограммы к объектам, все еще доминировали подпрограммы, а не соответствующие объекты.
@ Концепция OOП требует чтобы сначала указывался объект, а затем метод (подпрограмма). Пусть имеем пакет P:
|
@ и пусть объявлена некоторая переменная Y типа T, тогда чтобы применить процедуру Op к объекту Y на Аде 95 мы должны будем написать:
|
@ тогда как требование OOП требует такой нотации:
|
@ где объект Y на первом месте, а остальные вспомогательные параметры следуют в круглых скобках.
@ Реальное недовольство стилем ada состоит в том, что пакет P, содержащий объявление Op должен быть также упомянут. (Предполагается, что use-выражения не используются, как это часто бывает). Однако, зная тип объекта, мы можем найти его примитивные операции, и не совсем логично, чтобы при этом потребовать упоминания о пакете P. Кроме того, в случае сложной иерархии типов, не всегда очевидно для программиста, какой пакет содержит соответствующую операцию.
@ Префиксная нотация, указывающая объект сначала, теперь разрешена в Аде 2005. Основные правила для замены вызова подпрограммы в форме P.Op (Y...); на Y.Op (...); следующие:
@ У новой префиксной нотации есть и другие преимущества в унификации способов вызова функции или чтения компоненты тегового типа. Таким образом, рассмотрим следующий геометрический пример, который взят из (как мы надеемся, знакомого) учебника [6]
|
@ У типа Object есть две компоненты и две примитивных операции Area и MI (Area - область объекта, а MI - момент инерции). Здесь ключевой момент состоит в том, что с новой нотацией мы можем обратиться к координатам и области унифицированным способом. Например, пусть мы получаем конкретный тип Circle следующим образом:
|
@ где мы обеспечили конкретные операции для Area и MI. Тогда на Аде 2005 мы можем обратиться и к координатам и к области так:
|
@ Заметим, что, так как у Area есть только один параметр A_Circle то нет необходимости указывать круглые скобки при вызове функции. Эта унифицированность хорошо иллюстрируется телом MI, которое может быть написано так:
|
@ тогда как на Аде 95 мы должны были писать:
|
@ что не совсем красиво.
@ Аналогичное преимущество касается разыменования. Если у нас есть ссылочный тип:
|
@ и предположим, что мы желаем распечатать координаты и область. Тогда на Аде 2005 это будет выглядеть так:
|
@ тогда как на Аде 95 мы должны были написать:
|
@ На Аде 2005 разыменование all неявно, тогда как на Аде 95 любое разыменование должно быть явным, что не всегда удобно.
@ Злобный читатель может сказать, что вся эта болтовня может произвести впечатление только на слабаков, а не на махровых программистов. Тогда мы обратимся к теме множественного наследования. В Аде 95 множественное наследование весьма затруднено. Иногда оно может делаться использованием настраиваемых (generic) средств и/или ссылочных дискриминантов (не моя любимая тема), но это - весьма тяжелая работа и не всегда возможная. Таким образом, это большое удовольствие объявить, что Ада 2005 вводит реальное множественное наследование в стиле Java.
@ Основная проблема множественного наследования - конфликт между родителями. Он случается, если у нескольких родителей одни и те же компоненты (возможно, унаследованные от общего предка). Мы получаем две копии? И что случается если у обоих родителей одна и таже операция, но с различными реализациями? Связанные с этим проблемы преодолены посредством установления твердых ограничений для возможных свойств родителей. Это сделано введением понятия интерфейса.
@ Об интерфейсе можно думать как об абстрактном типе без компонентов - но у него, конечно, могут быть абстрактные операции. Также оказалось полезным ввести идею нулевой процедуры как операции тегового типа; мы не обязаны обеспечить фактическое тело для такой нулевой процедуры, но она ведет себя, как будто у нее есть тело, состоящее только из оператора null. Таким образом, мы могли бы написать:
|
@ Обратите внимание, что interface - новое зарезервированное слово. Мы можем теперь получить конкретный тип из интерфейса Int1
|
@ Мы можем обеспечить некоторые компоненты для DT (хотя это и не обязательно). Мы должны обеспечить конкретную процедуру для Op1 (т.к. мы объявили DT непосредственно как abstract). Но мы не должны выполнять замену (overriding) процедуры N, так как она ведет себя, как будто у неё есть конкретное пустое тело (но мы могли бы выполнить замену N, если бы захотели).
@ Мы можем фактически получить тип из нескольких интерфейсов плюс возможно один обычный теговый тип. Другими словами, мы можем получить теговый тип из нескольких других родительских типов, но только один из них может быть нормальным теговым типом (он должен быть упомянут первым). Мы именуем первого предка как родителя (таким образом, родитель может быть интерфейсом или нормальным тэговым типом), и любые другие как прародители (и они должны быть интерфейсами).
@ Таким образом, если Int2 - другой интерфейсный тип, а T1 - нормальный тэговый тип, то мы имеем право сделать следующее:
|
@ Возможно также получить интерфейсы из интерфейсов следующим образом:
|
@ Обратите внимание, что ключевое слово 'new' не используется в этой конструкции. У таких составных интерфейсов есть все операции всех их предков, и дальнейшие операции могут быть добавлены обычным способом, но они обязательно должны быть абстрактными или нулевыми.
@ Есть ряд простых правил когда у двух родительских интерфейсов одна и та же операция. В этом случае нулевая процедура перегружается как абстрактная, в противном случае у подобных операций должна быть одинаковая конфигурация.
@ Интерфейсы могут также быть обозначены как ограниченные:
|
@ Важное правило состоит в том, что потомок неограниченного интерфейса должен быть сам неограниченным. Обратное не верно.
@ Некоторые более обширные примеры использования интерфейсов будут даны в последующей публикации.
@ Нулевые процедуры введены не только для интерфейсов. Мы можем дать нулевую процедуру как спецификацию вообще, без конкретного тела процедуры. Но нулевые процедуры, в основном, связаны с теговыми типами и наследованием. Обратите внимание на пакет Ada.Finalization в Ada 2005:
|
@ Процедуры Initialize, Adjust, и Finalize теперь явно описаны как нулевые процедуры. Это только косметическое изменение с тех пор как в Аде 95 RM было заявлено, что заданные по умолчанию реализации не имеют никакого эффекта. Однако, это аккуратно разъясняет ситуацию и удаляет специальные семантические правила (прагма Preelaborable_Initialization будет объяснена в последующей публикации).
@ Другое важное изменение - возможность сделать расширение типа на уровне, более вложенном чем уровень родительского типа. Это означает, что управляемые (controlled) типы теперь могут быть объявлены на любом уровне, тогда как в Аде 95 начиная с пакета Ada.Finalization это было возможно только на библиотечном уровне. Есть подобные преимущества и в настраиваемых (generic) средствах. До сих пор в большинство настраиваемых средств могло быть реализовано только на библиотечном уровне.
@ Последнее изменение в области OOП, которая будет описано здесь это возможность указать явно, отменяет ли новая заменяемая операция существующую или нет.
@ В настоящее время в Аде 95 маленькие небрежные ошибки в конфигурациях подпрограммы могут привести к неудачным последствиям, причину которых бывает трудно определить. Это сильно противоречит основополагающей концепции языка Ада поощрять написание правильных программ и обнаруживать большинство ошибок на этапе трансляции, когда это возможно. Рассмотрим:
|
@ Здесь у нас есть тип Controlled, плюс операция Op этого типа. Кроме того, мы намеревались перегрузить автоматически наследуемую нулевую процедуру Finalize из Controlled, но хлопнув ушами, мы перепутали буквы и написали Finalse. Таким образом, наша новая процедура не перегружает Finalize вообще, а просто объявляет другую операцию с новым именем. В этом случае при автоматическом завершении типа T будет вызываться нулевая процедура, а не наш собственный код. Этот вид ошибок может быть очень трудно обнаружить.
@ В Аде 2005 мы можем защититься от таких ошибок, пометив перегружаемые операции следующим образом:
|
@ И теперь если мы перепутаем буквы в имени процедуры Finalize то компилятор легко обнаружит ошибку. Заметим, что overriding - ещё одно новое ключевое слово в Ада 2005. Однако, частично по причинам совместимости, использование квалификатора overriding не является обязательным; здесь имеются также более хитрые особености связанные с приватными типами и настраиваемыми средствами, которые будут обсуждаться в последующих публикациях.
@ Аналогичные проблемы могут возникнуть, если мы понимаем конфигурацию превратно. Предположим, что мы получаем новый тип из T и пытаемся перегрузить Op следующим образом:
|
@ Здесь мы объявили процедуру Op правильно, но её спецификация отличается от родительской, потому что параметр Data был ошибочно объявлен типа String, а не Integer. Таким образом, новая версия Op просто будет перегрузкой (overloading), а не заменой (overriding). И снова мы можем принять меры против этого вида ошибок записав:
|
@ С другой стороны, возможно, мы действительно хотели обеспечить новую операцию. В этом случае мы можем написать not overriding, и компилятор тогда гарантирует, что новая операция - действительно не может быть заменена:
|
@ Использование индикатора overriding предотвращает ошибки на этапе компиляции. Таким образом, если позже мы добавим новый параметр для Op для корневого типа T тогда, использование индикаторов гарантирует, что мы изменяем все производные типы соответственно.
2010-10-24 00:26:52
. .