Можно ли добавить и реализовать интерфейс к уже существующему классу (который является потомком TInterfaced
или TInterfacedPersistent
), чтобы выполнить разделение модели и представления на 2 единицы?
Небольшое объяснение, почему мне нужно что-то вроде этого:
Я разрабатываю древовидную структуру открытого типа, которая имеет следующую структуру (ОЧЕНЬ упрощенная и неполная, просто для иллюстрации контура проблемы):
Database_Kernel.pas
TVMDNode = class(TInterfacedPersistent);
public
class function ClassGUID: TGUID; virtual; abstract; // constant. used for RTTI
property RawData: TBytes {...};
constructor Create(ARawData: TBytes);
function GetParent: TVMDNode;
function GetChildNodes: TList<TVMDNode>;
end;
Vendor_Specific_Stuff.pas
TImageNode = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property Image: TImage {...};
end;
TUTF8Node = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property StringContent: WideString {...};
end;
TContactNode = class(TVMDNode)
public
class function ClassGUID: TGUID; override; // constant. used for RTTI
// Will be interpreted out of the raw binary data of the inherited class
property PreName: WideString {...};
property FamilyName: WideString {...};
property Address: WideString {...};
property Birthday: TDate {...};
end;
Используя RTTI с использованием GUID (который использует ClassGUID
), функция GetChildNodes
может найти соответствующий класс и инициализировать его необработанными данными. (Каждый набор данных содержит ClassGUID
и RawData
рядом с другими данными, такими как созданные/обновленные временные метки)
Важно заметить, что мой API (Database_Kernel.pas
) строго отделен от классов node поставщика (Vendor_Specific_Stuff.pas
).
GUI программы, зависящей от поставщика, хочет визуализировать узлы, например. давая им удобное имя, значок и т.д.
Следующие идеи работают:
IGraphicNode = interface(IInterface)
function Visible: boolean;
function Icon: TIcon;
function UserFriendlyName: string;
end;
Относящиеся к поставщику потомки TVMDNode
в Vendor_Specific_Stuff.pas
будут реализовывать интерфейс IGraphicNode
.
Но поставщику также необходимо изменить Database_Kernel.pas
для реализации IGraphicNode
в базовый класс node TVMDNode
(который используется для "неизвестных" узлов, где RTTI не удалось найти соответствующий класс набора данных, поэтому, по крайней мере, двоичные исходные данные могут быть прочитаны с помощью TVMDNode.RawData
).
Поэтому он изменит мой класс следующим образом:
TVMDNode = class(TInterfacedPersistent, IGraphicNode);
public
property RawData: TBytes {...};
class function ClassGUID: TGUID; virtual; abstract; // constant. used for RTTI
constructor Create(ARawData: TBytes);
function GetParent: TVMDNode;
function GetChildNodes: TList<TVMDNode>;
// --- IGraphicNode
function Visible: boolean; virtual; // default behavior for unknown nodes: False
function Icon: TIcon; virtual; // default behavior for unknown nodes: "?" icon
function UserfriendlyName: string; virtual; // default behavior for unknown nodes: "Unknown"
end;
Проблема заключается в том, что IGraphicNode
зависит от поставщика/программы и не должен находиться в API Database_Kernel.pas
, так как GUI и Model/API должны быть строго разделены.
Мое желание состояло в том, что interace IGraphicNode
можно было бы добавить и реализовать в существующий класс TVMDNode
(который уже является потомком TInterfacedPersistent
для разрешения интерфейсов) в отдельном модуле. Насколько мне известно, Delphi не поддерживает что-то вроде этого.
Помимо того, что смешивать модель и представление в одном отдельном модуле/классе нехорошо, будет существовать следующая реальная проблема: если поставщик должен изменить мой API Database_Kernel.pas
для расширения TVMDNode
с помощью IGraphicNode
, ему необходимо повторно выполнить все его изменения, как только я выпущу новую версию своего API Database_Kernel.pas
.
Что мне делать? Я очень долго думал о возможных решениях с Delphi OOP. Обходной путь может вставлять TVMDNode в класс контейнера, который имеет вторичный RTTI, поэтому после того, как я нашел класс TVMDNode
, я мог бы искать класс TVMDNodeGUIContainer
. Но это звучит очень душно и как грязный хак.
PS: Этот API представляет собой проект OpenSource/GPL. Я стараюсь оставаться совместимым со старыми поколениями Delphi (например, 6), так как я хочу максимизировать число возможных пользователей. Однако, если решение проблемы выше возможно только с новым поколением языков Delphi, я мог бы рассмотреть возможность отмены поддержки Delphi 6 для этого API.