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

       

Родственное наследование


В дополнение к показанному выше способу, можно использовать несколько специализированный способ родственного наследования (sibling inheritance), с применением ссылочных дискриминантов.

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

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

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

В терминах типов, такая идея рассматривает концептуальный тип C, производный от двух типов A и B, как множество типов C_A и C_B, соответственно производных от A и B.

Объекты концептуального типа C создаются путем совместной генерации множества объектов типа C_A и типа C_B.

Такие объекты называют родственными объектами (sibling objects) или сдвоенными объектами (twin objects).

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

Они также доступны индивидуально.

Таким образом, легко доступно частичное представление множества, которое соответствует отдельному объекту.

В качестве примера, рассмотрим создание концептуального типа Controlled_Humans, производного от стандартного типа Controlled и типа Human.

Компонент First_Name - это ссылочный тип, а не строка фиксированной длины.

Таким образом, имя может иметь динамически изменяемую длину.

В данном случае используются средства контролируемых объектов для освобождения памяти ассоциируемой с First_Name при разрушении объекта типа Human.

Спецификация типа Human может иметь подобный вид:

package Dynamic_Humanity is

type String_Ptr is access String; type Human is tagged limited

record

First_Name: String_Ptr; end record;

procedure Christen (H: in out Human; N: in String_Ptr); -- устанавливает имя для Human

... end Dynamic_Humanity;

<


Для построения комбинации из этих двух типов, необходимо создать два родственных типа Human_Sibling и Controlled_Sibling, путем соответственного производства от Human и Limited_Controlled.

Два этих типа, вместе формируют концептуальный тип Controlled_Humans:

with Dynamic_Humanity, Ada.Finalization;

package Controlled_Human is

type Human_Sibling; -- ввиду взаимозависимости типов, необходимо использование -- неполного описания типа

type Controlled_Sibling (To_Human_Sibling: access Human_Sibling) is

new Ada.Finalization.Limited_Controlled with null record; -- тип To_Human_Sibling является связкой с Human_Sibling

procedure Finalize (C: in out Controlled_Sibling);

type Human_Sibling is new Dynamic_Humanity.Human with

record

To_Controlled_Sibling: Controlled_Sibling (Human_Sibling'Access); -- To_Controlled_Sibling является связкой с Controlled_Sibling. -- Этот компонент автоматически инициализируется ссылкой -- на текущее значение экземпляра Human_Sibling end record;

-- примитивные операции типа Human (могут быть переопределены)

end Controlled_Human;

Таким образом, эти типы - взаимосвязаны, и для обращения от одного сдвоенного объекта к другому:

Human_Sibling обладает компонентом связки с To_Controlled_Sibling

который подходит к типу Controlled_Sibling.



  • Controlled_Sibling обладает компонентом связки с To_Human_Sibling

    который подходит к типу Human_Sibling.

    Следует заметить, что эти связки имеют различную природу.

    Связка To_Controlled_Sibling - это связка членства: любой объект типа Controlled_Sibling заключен в каждый объект типа Human_Sibling.

    Связка, To_Human_Sibling - это ссылочная связка: To_Human_Sibling обозначает Human_Sibling.

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

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



    • Последовательная генерация объектов


    Поскольку To_Controlled_Sibling - это компонент Human_Sibling, любой объект типа Controlled_Sibling каждый раз создается автоматически при создании объекта типа Human_Sibling.

    Связка To_Controlled_Sibling автоматически инициализируется ссылкой на заключенный объект, поскольку атрибут 'Access применяется к имени типа записи, внутри описания, автоматически обозначая текущий экземпляр типа.

    Как результат, описание:

      CH: Human_Sibling;

      автоматически объявляет объект концептуального типа Controlled_Human

      • Переопределение операций


      Примитивные операции могут быть переопределены тем сдвоенным типом который их реализует (например, Finalize). Например, для предотвращения утечки памяти, мы переопределяем Finalize

      для автоматической очистки памяти, используемой First_Name, когда Human становится не доступным:

      package body Controlled_Human is

      procedure Free is new Unchecked_Deallocation (String_Ptr);

      procedure Finalize (C: in out Controlled_Sibling) is

      -- overrides Finalize inherited from Controlled

      begin

      Free (C.To_Human_Sibling.all.First_Name); end Finalize;

      end Controlled_Human;

      • Доступ ко всем компонентам и операциям


      Компоненты каждого сдвоенного объекта могут быть выбраны и использованы любым из сдвоенных объектов, используя связки. Например, операция Finalize

      (описанная для Controlled_Sibling) может использовать компонент First_Name

      (описанный для Human_Sibling).

      Примитивные операции могут быть вызваны тем же самым способом.


    • Добавление свойств


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

    • Расширение типа


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

    Это важно при производстве от сдвоенного типа Human_Sibling, который содержит компоненты другого сдвоенного типа.

    Свойства другого сдвоенного типа также доступны для производства через связку To_Human_Sibling.



    • Проверка принадлежности


    Проверка принадлежности объекта к любому из типов предков выполняется путем использования согласованного (надклассового) представления любого из сдвоенных объектов:

    declare

    CH: Human_Sibling; -- simultaneous object generation

    begin

    ... CH in Human'Class ... -- True

    ... CH.To_Controlled_Sibling.all

    in Limited_Controlled'Class ... -- True

    end;

    • Присваивание


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

    Такая модель может быть легко расширена для управления множественным наследованием от более чем двух типов.


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