Ада-95. Компилятор GNAT

       

Динамическая диспетчеризация


При внимательном рассмотрении показанной ранее реализации процедуры Show

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

В данном случае, подразумевается, что если мы решим описать новый тип, производный от любого типа входящего в показанную иерархию (Root, Child_1, Child_2 и Grand_Child_2_1), то результатом работы такой реализации процедуры Show

всегда будет сообщение "Unknown type", извещающее о том, что фактический тип параметра Self - не известен.

Например, такая ситуация может возникнуть когда мы опишем новый тип Grand_Child_1_1, производный от типа Child_1, в каком-либо другом пакете.

С первого взгляда может показаться, что будет достаточно переписать процедуру Show

с учетом нового типа Grand_Child_1_1.

Действительно, такое решение будет работать, но это значит, что при описании каждого нового типа, входящего в эту иерархию, мы вынуждены вносить изменения в уже написанные и отлаженные модули.

Для того, чтобы избавиться от таких трудностей, реализацию процедуры Show

надо действительно переписать, но так, чтобы задействовать механизм динамической диспетчеризации вызовов подпрограмм, который предоставляет использование тэговых типов.

Вспомним, что при демонстрации примера иерархии типов, был также приведен пример спецификации пакета Simple_Objects, в котором эта иерархия типов описывается.

Теперь, перед непосредственным обсуждением механизма динамической диспетчеризации вызовов подпрограмм, предположим, что описание тела этого пакета имеет следующий вид:

with Ada.Text_IO;

package body Simple_Objects is

-- примитивные операции типа Root

function The_Name (Self: in Root) return String is

begin

return ("Root"); end The_Name;

procedure Show (Self: in Root'Class) is

begin

Ada.Text_IO.Put_Line ( The_Name(Self) ); end Show;

-- примитивные операции типа Child_1

function The_Name (Self: in Child_1) return String is

begin

return ("Child_1"); end The_Name;

-- примитивные операции типа Child_2

function The_Name (Self: in Child_2) return String is

begin

return ("Child_2"); end The_Name;

-- примитивные операции типа Grand_Child_2_1

function The_Name (Self: in Grand_Child_2_1) return String is

begin

return ("Grand_Child_2_1"); end The_Name;

end Simple_Objects;

<


Не сложно догадаться, что особое внимание следует обратить на реализацию процедуры Show, которая теперь, перед вызовом Ada.Text_IO.Put_Line, выдающим строку сообщения, вызывает функцию The_Name, возвращающую строку которая, собственно, содержит текст сообщения.

Заметим также, что важным моментом в этой реализации является то, что процедура Show

имеет формальный параметр надклассового типа, что позволяет ей принимать в качестве фактического параметра любые объекты тип которых принадлежит данной иерархии типов.

При дальнейшем внимательном рассмотрении спецификации и тела пакета Simple_Objects, следует обратить внимание на то, что функция The_Name описана для всех типов иерархии (Root, Child_1, Child_2 и Grand_Child_2_1) и является примитивной операцией для этих типов.

Напомним, что в случае тэговых типов, примитивные операции являются потенциально диспетчеризуемыми операциями.

В результате, процедура Show, принимая во время выполнения программы фактический параметр, осуществляет диспетчеризацию вызова соответствующей реализации функции The_Name

на основании информации о тэге фактического параметра.

Это значит, что в данном случае присутствует динамическое связывание которое обеспечивает динамическую полиморфность поведения объектов.

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

  • подпрограмма должна иметь формальный параметр тэгового типа
  • при вызове подпрограммы, фактический параметр, соответствующий параметру тэгового типа должен быть объектом надклассового типа

    Следовательно, если фактический надклассовый тип представляет объекты двух или более индивидуальных типов, которые имеют подпрограммы с одинаковыми параметрами (исключая параметр надклассового типа), то определение реализации подпрограммы на этапе компиляции (иначе, статическое связывание) не возможно.

    Вместо этого, осуществляется динамическое связывание во время выполнения программы.

    Возвращаясь к проблеме с которой мы начинали обсуждение диспетчеризации, следует заметить, что использование этого механизма в процедуре Show, позволяет теперь описывать новые типы, производные от любого типа входящего в иерархию, без необходимости изменения реализации процедуры Show.



    Действительно, при описании нового производного типа, достаточно описать соответствующую реализацию функции The_Name для нового типа, не внося никаких изменений в ранее написанные модули.

    Заметим также, что если при описании нового производного типа не будет предусмотрена реализация функции The_Name, то для этого типа процедура Show, используя диспетчеризацию, будет вызывать реализацию функции The_Name унаследованную от типа предка.

    Мы рассмотрели случай динамической диспетчеризации в подпрограмме при описании которой используется формальный параметр надклассового типа.

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

    ... The_Name(Any_Instance.all)... -- Any_Instance может обозначать объект который принадлежит любому -- типу класса Root'Class

    Еще одним случаем диспетчеризации является использование переменной надклассового типа. В этом случае нужно обратить внимание на отличие диспетчеризуемого вызова от обычного вызова:

    declare

    Instance_1 : Root; Instance_2 : Root'Class ... ; begin

    ... The_Name(Instance_1)... -- статическое связывание: компилятор знает индивидуальный тип -- Instance_1, поэтому он может определить реализацию

    ... The_Name(Instance_2)... -- динамическое связывание: Instance_2 может принадлежать любому -- типу класса Root'Class

    -- поэтому компилятор не может точно определить реализацию -- подпрограммы, она будет выбрана во время выполнения программы end ;


    Содержание раздела