Динамическая диспетчеризация
При внимательном рассмотрении показанной ранее реализации процедуры 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 ; |