Родственное наследование
В дополнение к показанному выше способу, можно использовать несколько специализированный способ родственного наследования (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; |
- Присваивание
Любой объект концептуального типа может быть присвоен объекту обоих типов предков обычным образом, то есть, путем выбора в концептуальном объекте сдвоенного объекта, который совпадает по типу с требуемым предком (используя преобразование представления), и присваивая этот сдвоенный объект назначению операции присваивания.
Такая модель может быть легко расширена для управления множественным наследованием от более чем двух типов.