Rationale for Ada 2005: Object oriented model
RUSTOPBACKNEXT
ENG |
6. Object factory functions
@ The Ada 95 Rationale (Section 4.4.1) [2] says "We also note that object oriented programming requires thought especially if variant programming is to be avoided. There is a general difficulty in finding out what is coming which is particularly obvious with input–output; it is easy to write dispatching output operations but generally impossible for input." In this context, variant programming means messing about with case statements and so on. @ The point about input–output is that it is easy to write a heterogeneous file but not so easy to read it. @ In the simple case of a text file we can just do a series of calls of Put thus Put ("John is "); Put(21, 0); Put(" years old."); But text input is not so easy unless we know the order of the items in the file. If we don't know the order then we really have to read the wretched thing a line at a time and then analyse the lines. @ Ada 95 includes a mechanism for doing this relatively easily in the case of tagged types and stream input–output. Suppose we have a class of tagged types rooted at Root with various derived specific types T1, T2 and so on. We can then output a sequence of values X1, X2, X3 of a variety of these types to a file identified by the stream access value S by writing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Rationale for Ada 2005: Object oriented model
@ENGRUSTOPBACKNEXT6. Функции фабрики объектов
@ Разъяснение для ada (Раздел 4.4.1) [2] говорит "Объектно-ориентированное программирование требует избегать вариантного программирования. Здесь имеется большая трудность в обнаружении, которая особенно очевидна в случае с вводом - выводом; весьма просто написать операцию диспетчеризации вывода, но вообще невозможно это для ввода." В этом контексте вариантное программирование означает иметь дело с неудобными и потенциально опасными операторами выбора и так далее.
@ Проблема ввода-вывода состоит в том, что весьма просто написать гетерогенный файл, но не так просто читать его.
@ В простом случае текстового файла мы легко можем сделать серию операторов Put ("John is "); Put(21, 0); Put(" years old."); Но ввод текста не так прост если мы не знаем порядок элементов в файле. В этом случае, мы должны при чтении каждой строки анализировать её содержимое.
@ Ада 95 включает механизм для того чтобы делать это относительно легко в случае потокового ввода - вывода теговых типов. Предположим что у нас есть класс теговых типов внедренных в Root с различными унаследованными определенными типами T1, T2, и так далее. Тогда мы можем вывести последовательность значений X1, X2, X3 соответствующих типов в файл, идентифицированный ссылкой S:
|
@ При этом каждый вызов Output сначала пишет тэг определенного типа а затем значение этого типа. Тэг соответствующий типу T1 является строкой External_Tag (T1'Tag). Напомним, что External_Tag - функция в предопределенная в пакете Ada.Tags.
@ При вводе мы делаем следующее:
|
@ Вызов Root'Class'Input сначала читает внешний тэг, а затем обращается к соответствующей функции Tn'Input согласно значению тэга. Функция читает значение, которое устанавливается как начальное значение для надклассовой переменной X. Тогда мы можем сделать всё что захотим с X, например, послать в процедуру Process в соответствии с установленным типом.
@ Это нормально работает в Аде 95, но всё это скрыто в реализации. Основные методики к сожалению не доступны для пользователя.
@ Это означает, что если мы хотим разработать наш собственный потоковый протокол или, возможно, только обработать несколько значений при обстоятельствах где мы не можем непосредственно использовать диспетчеризацию, тогда мы должны сделать все это непосредственно условными операторами или операторами выбора. Таким образом, нам надо задать значение тэга и фактическое значение которое будет назначаться переменной соответствующего типа. На Аде 95 мы бы делали это как:
|
@ Мы предполагаем, что Get_T - примитивная функция класса, внедренного в Root. Т.е. для каждого определенного типа имеется своя функция Get_T, выбор которой в условных операторах осуществляется во время компиляции по нормальным правилам перегрузки. Здесь процедура Process - примитивная подпрограмма типа класса.
@ Это все очень утомительно и нуждается в постоянном внимании если мы добавляем дальнейшие типы к классу.
@ Ада 2005 преодолевает эту проблему, обеспечивая настраиваемую объектную функцию конструктор со следующей спецификацией:
|
@ Эта настраиваемая функция работает и с ограниченными и неограниченными типами. Напомним, что неограниченный тип разрешается как фактический настраиваемый параметр, соответствующий ограниченному формальному настраиваемому типу. Настраиваемая функция Generic_Dispatching_Constructor есть Pure и имеет соглашение Intrinsic.
@ Обратите внимание на формальную функцию Constructor. Это пример нового вида формального настраиваемого параметра, введенного в Аде 2005. Его отличительная особенность - использование ключегового слова abstract в спецификации. Его интерпретация состоит в том, что фактическая функция должна быть операцией диспетчеризации тегового типа, однозначно определенного конфигурацией формальной функции. Фактическая операция может быть конкретной или абстрактной. Напомним, что правила замены гарантируют, что у определенной операции для любого конкретного типа всегда будет конкретное тело. Отметим, что так как операция абстрактна, её можно вызвать только через диспетчеризацию.
@ В этом примере это должна быть операция диспетчеризации типа T, так как это - единственный теговый тип, вовлеченный в конфигурацию Constructor. Мы говорим, что T - управляемый тип. В общем случае управляемый тип не должен самостоятельно быть формальным параметром универсального модуля, но обычно будет столь здесь. Кроме того, отметим, что хотя операция должна быть операцией диспетчеризации, это не примитив и так, если мы произойдем из типа T то это не будет наследовано.
@ Формальные абстрактные подпрограммы конечно могут быть процедурами так же как и функциями. Важно, что есть точно один управляемый тип в конфигурации. Таким образом, учитывая что TT1 и TT2 теговые типы, тогда следующее было бы незаконно:
|
@ Процедура Do_This незаконна, потому что у нее есть два управляемых типа TT1 и TT2. Напомним, что мы можем объявить подпрограмму с параметрами больше чем одного тегового типа, но это может быть только операция диспетчеризации одного тегового типа. Функция Fn незаконна, потому что у нее нет никаких управляемых типов вообще (так как она никогда не может быть вызывана в запросе диспетчеризации так или иначе).
@ Формальная функция Constructor правильна, потому что только тип T теговый; Тип Parameters, который также входит в его профиль, не теговый.
@ И теперь возвратимся к конструктору диспетчеризации. Его идея состоитв том, что мы проиллюстрировали настраиваемую функцию с (коревым) теговым типом T и некоторым типом Parameters и функцией Constructor диспетчеризации. Тип Parameters обеспечивает средство, посредством которого вспомогательную информацию можно передать функции Constructor.
@ Настраиваемая функция Generic_Dispatching_Constructor имеет два параметра, первый - тэг типа объекта, который будет создан, а другой вспомогательную информацию, которая передаётся диспетчерской функции Constructor.
@ Отметим, что тип Parameters используется как ссылочный параметр и в настраиваемой функции и в формальной функции Constructor. Это потому для того чтобы это могло быть согласовано конфигурации атрибута Input со спецификацией:
|
@ Предположим, что мы проиллюстрировали Generic_Dispatching_Constructor, чтобы дать функцию Make_T. Запрос Make_T берет значение тэга, определённого к соответствующим Constructor, который передаёт значение определенного тегового типа соответствующемуо тэгу и это наконец возвращается как значение надклассового типа T'Class как результат Make_T. Это все еще волшебство, но любой может использовать это волшебство а не только фокусник осуществляющий потоковый ввод - вывод.
@ Мы можем теперь решить свою абстрактную проблему следующим образом:
|
@ У нас больше нет утомительной последовательности условных операторов, и запросы Get_T и Process посылают запросы.
@ Теперь волшебная функция T'Class'Input может быть осуществлена весьма естественным способом:
|
@ Тело могло конечно быть написано как один гигантский оператор return Dispatching_Input (Descendant_Tag (String'Input (S), T'Tag), S), но краткая форма здесь более наглядна.
@ Отметим использование Descendant_Tag, а не Internal_Tag.Descendant_Tag - одна из нескольких новых функций, введенных в пакет Ada.Tags в Аде 2005. Потоки не работали идеально с вложенными теговыми типами в Аде 95 из-за возможности множественной элаборации объявлений (в результате управления задачами и рекурсии); это означало, что у двух настраиваемых типов могло быть одно и то же внешнее значение тэга, и Internal_Tag не мог отличить их. Это не самая важная проблема в Аде 95 т.к. вложенные теговые типы там редко используются. В Аде 2005 ситуация потенциально хуже из-за возможности вложенного расширения типа.
@ Цель Ады 2005 проста - гарантировать что потоки действительно работают с типами, объявленными на том же самом уровне и предотвратить ошибочное поведение. Цель не состоит в том, чтобы разрешить потокам работать с вложенными расширениями введенными в Аде 2005. Любая попытка сделать так приведет к возбуждению исключения Tag_Error.
@ Отметим, что мы не можем фактически объявить функцию атрибута, такую как T'Class'Input непосредственно используя название атрибута. Мы должны использовать некоторый другой идентификатор, такой как T_Class_Input и затем использовать выражение определения атрибута как показано выше.
@ Заметим, что T'Class'Output может быть осуществлен как:
|
@ Напомним, что потоки спроектированы чтобы работать только с типами, объявленными на том же самом уровне доступности что и родительский тип T. Вызов Is_Descendant_At_Same_Level, который является другой новой функцией в Аде 2005, гарантирует это.
@ Мы можем использовать настраиваемый конструктор для создания нашего собственного потокового протокола. Мы можем фактически заменить T'Class'Input и T'Class'Output или только создать нашу собственную отличную подсистему. Одна причина, почему мы могли бы захотеть использовать другой протокол - это то что у нас имелся бы уже один, например - XML.
@ Отметим, что иногда не будет необходимости передавать какие-либо вспомогательные параметры для функции конструктора, когда мы можем объявить:
|
@ Другой пример может базироваться на программе Magic Moments из [3] в которой читаются значения, необходимые для создания различных геометрических объектов, таких как Круг, Треугольник, или Квадрат, которые получены из абстрактного типа Object. Значениям предшествуют символы 'C', 'T' или 'S' соответственно.
|
@ Типы Circle, Triangle и Square получены из корневого типа Object, а Object_Ptr ссылочного типа Object'Class. Функция Get_Circle читает значение радиуса с клавиатуры, функция Get_Triangle читает значения длин этих трех сторон с клавиатуры и так далее.
@ Первое необходимое действие состоит в том чтобы изменить различные функции конструктора, такие как Get_Circle в различные определенные замены (overridings) примитивной операции Get_Object так чтобы мы могли послать на этом.
@ Вместо непосредственного чтения кодового знака мы могли бы заставить пользователя печатать внешнюю строку тэга, и затем мы могли бы иметь
|
@ но это очень утомительно, потому что пользователь теперь должен напечатать внешний тэг, который будет определенной реализацией комбинации символов. Заметим, что строка, произведенная запросом Expanded_Name, такая как OBJECTS.CIRCLE, не может использоваться, потому что она вообще не будет уникальной и, таким образом, нет никакой обратной функции. (Это вообще не уникально из-за управления задачами и рекурсии). Но Expanded_Name полезен для отладочных целей.
@ При этих обстоятельствах лучший способ состоит в том, чтобы изобрести своего рода регистрационную систему, чтобы сделать карту, чтобы преобразовать простые кодовые знаки в тэг. У нас мог бы быть пакет
|
@ и мы можем написать:
|
@ И теперь программа упрощается, она сначала читает код а затем создаёт объект
|
@ и не нужно городить огород из операторов выбора.
@ Действительно важный пункт об этом примере - то, что, если мы решим позднее добавить ещё типов, таких как 'P' для Pentagon и 'H' для Hexagon тогда всё что мы должны сделать, это зрегистрировать новые кодовые знаки следующим образом:
|
@ и ничего больше не нужно менять. Эта регистрация может быть удобно сделана, когда типы объявлены.
@ Пакет Tag_Registration мог быть осуществлен тривиально так:
|
@ Константа No_Tag - значение типа Tag, которая не представляет фактический тэг. Если мы забудем регистрировать тип тогда значение No_Tag будет возвращено функцией Decode, и это заставит Make_Object возбуждать исключение Tag_Error.
@ Более изящная регистрационная система могла быть легко осуществлена использованием контейнерной библиотеки, которая будет описана в более поздней статье.
@ Отметим, что любой экземпляр Generic_Dispatching_Constructor проверяет что тэг переданый как параметр действительно является экземпляром типа класса, унаследованного из корневого типа T и возбуждает исключение Tag_Error, если это не так.
@ В простых случаях мы могли фактически представить это как проверку орфографии при написании:
|
@ Функция Parent_Tag и константа No_Tag - дальнейшие элементы пакета Ada.Tags, спецификация которых в Аде 2005 следущая:
|
@ Функция Parent_Tag возвращает No_Tag, если у параметра T типа Tag нет никакого родителя, который будет является окончательным корневым типом класса. Как было упомянуто ранее, две других новых функции Descendant_Tag и Is_Descendant_At_Same_Level необходимы для предотвращения неправильного употребление потоков с типами не объявленными на этом же уровне.
@ Есть также функция Interface_Ancestor_Tags, которая возвращает тэги из всех тех интерфейсов, которые являются предками T как массив. Он включает родителя, если это - интерфейс, прародители и все их предки, которые являются интерфейсами также - но оно исключает тип T непосредственно.
@ Наконец отметим, что введение 16-и 32-разрядных символов в идентификаторах означает, что также должны быть предоставлены функции, чтобы возвратить изображения идентификаторов в формате Wide_String или Wide_Wide_String. Таким образом, у нас есть функции Wide_Expanded_Name и Wide_Wide_Expanded_Name такие же как и Expanded_Name. Нижняя граница строк, возвращаемая этими функциями и External_Tag - 1 (еденица) (в Аде 95 забыли заявить это для External_Tag и Expanded_Name!)
2010-10-31 17:15:52
. .