之后添加一个接口到一个类 [英] Add an interface to a class afterwards
问题描述
是否可以添加并实现一个已经存在的类的接口(它是 TInterfaced
或 TInterfacedPersistent
)完成将Model和View分为2个单元?
一个小的解释为什么我需要这样的东西:
我正在开发一个树型结构的开放式模型,它具有以下结构(非常简单和不完整,仅用于说明问题的大纲):
Database_Kernel.pas
TVMDNode = class(TInterfacedPersistent);
public
class function ClassGUID:TGUID;虚拟;抽象; // 不变。用于RTTI
属性RawData:TBytes {...};
构造函数Create(ARawData:TBytes);
函数GetParent:TVMDNode;
函数GetChildNodes:TList< TVMDNode> ;;
结束
Vendor_Specific_Stuff.pas
TImageNode = class(TVMDNode)
public
类函数ClassGUID:TGUID;覆盖// 不变。用于RTTI
//将被解释为继承类的原始二进制数据
属性Image:TImage {...};
结束
TUTF8Node = class(TVMDNode)
public
类函数ClassGUID:TGUID;覆盖// 不变。用于RTTI
//将被解释为继承类的原始二进制数据
属性StringContent:WideString {...};
结束
TContactNode = class(TVMDNode)
public
类函数ClassGUID:TGUID;覆盖// 不变。用于RTTI
//将被解释为继承类的原始二进制数据
属性PreName:WideString {...};
属性FamilyName:WideString {...};
property地址:WideString {...};
property生日:TDate {...};
结束
使用基于GUID的RTTI(使用 ClassGUID
),函数 GetChildNodes
能够找到匹配的类并使用原始数据进行初始化。 (每个数据集包含 ClassGUID
和 RawData
除了其他数据,如创建/更新的时间戳)
重要的是要注意,我的API( Database_Kernel.pas
)与供应商的节点类( Vendor_Specific_Stuff)严格分开。 pas
)。
供应商特定程序的GUI希望可视化节点,例如给他们一个用户友好的名字,一个图标等。
以下想法:
code> IGraphicNode = interface(IInterface)
function Visible:boolean;
功能图标:TIcon;
function UserFriendlyName:string;
结束
供应商的特定后代 TVMDNode
code> Vendor_Specific_Stuff.pas 将实现 IGraphicNode
界面。
但是供应商还需要更改 Database_Kernel.pas
,以实现 IGraphicNode
到基本节点类 TVMDNode
(用于未知节点,其中RTTI无法找到数据集的匹配类,因此至少可以使用 TVMDNode.RawData读取二进制原始数据
)。
所以他会改变我的课程如下:
TVMDNode = class(TInterfacedPersistent,IGraphicNode);
public
属性RawData:TBytes {...};
类函数ClassGUID:TGUID;虚拟;抽象; // 不变。用于RTTI
构造函数Create(ARawData:TBytes);
函数GetParent:TVMDNode;
函数GetChildNodes:TList< TVMDNode> ;;
// --- IGraphicNode
function Visible:boolean;虚拟; //未知节点的默认行为:False
function图标:TIcon;虚拟; //未知节点的默认行为:? icon
function UserfriendlyName:string;虚拟; //未知节点的默认行为:未知
end;
问题是 IGraphicNode
是供应商/程序特定,不应该在API的 Database_Kernel.pas
中,因为GUI和Model / API应该严格分开。
我的愿望是,可以将现有的 TVMDNode
类添加并实现interace IGraphicNode
已经是 TInterfacedPersistent
的后代允许接口)在一个单独的单位。据我所知,Delphi不支持这样的东西。
除了在一个单一的单元/类中混合模型和视图不是很好,将会遇到真正的问题:如果供应商必须更改我的 Database_Kernel.pas
API以将 TVMDNode
IGraphicNode
界面,他需要重新执行所有的更改,一旦我发布了一个新版本的我的API Database_Kernel.pas
。
我该怎么办?我非常想,可能的解决方案可能与德尔福的OOP。解决方法可能是将TVMDNode嵌套到容器类中,该类具有辅助RTTI,因此在找到 TVMDNode
类之后,我可以搜索 TVMDNodeGUIContainer
类。但是这听起来很扼杀,就像一个肮脏的黑客。
PS:这个API是一个OpenSource / GPL项目。我试图与旧版Delphi保持兼容(例如6),因为我希望最大化可能的用户数量。然而,如果以上问题的解决方案只能用新一代Delphi语言,我可能会考虑删除Delphi 6对此API的支持。
如果您要应用工厂设计模式。
对于每个节点类,将有一个类工厂一个函数指针)负责创建正确的Delphi类。类工厂可以在单元初始化部分(每次应用程序启动一次)在内核单例对象注册。
然后,内核单例会将GUID映射到正确的工厂,转动调用正确的类实例构造函数(如 http://delphipatterns.blog .com / 2011/03/23 / abstract-factory )
软件包可能会被拆分成单独的DLL,并以不同的单元实现,仍然继承自基础TVMNode类。
您现在可以使用RTTI的功能可以通过一些虚拟方法轻松地支持后代类或工厂类。
您还可以考虑使用更简单的数据传输对象用于保存/加载TVMNodes,也可能从a获得灵感已经很好地了解了对象关系映射器或对象持久性框架作为您正在尝试解决的问题,正如我们正在处理的问题(已经)
我不知道这个类的好的Delphi开源框架。但是从其他语言,您可以查看 Java Hibernate , Microsoft .NET实体框架或简约 Google协议缓冲区序列化程序
Is it possible to add and implement an interface to an already existing class (which is a descendant of TInterfaced
or TInterfacedPersistent
) to accomplish separating Model and View into 2 units?
A small explanation why I need something like this:
I am developing a tree-structure, open-type model, which has following structure (VERY simplified and incomplete, just to illustrate the outline of the problem):
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;
Using a GUID-based RTTI (which uses ClassGUID
), the function GetChildNodes
is able to find the matching class and initialize it with the raw data. (Each dataset contains ClassGUID
and RawData
beside other data like created/updated timestamps)
It is important to notice that my API (Database_Kernel.pas
) is strictly separated from the vendor's node classes (Vendor_Specific_Stuff.pas
).
A vendor-specific program's GUI wants to visualize the nodes, e.g. giving them an user-friendly name, an icon etc.
Following idea works:
IGraphicNode = interface(IInterface)
function Visible: boolean;
function Icon: TIcon;
function UserFriendlyName: string;
end;
The vendor's specific descendants of TVMDNode
in Vendor_Specific_Stuff.pas
will implement the IGraphicNode
interface.
But the vendor also needs to change Database_Kernel.pas
to implement IGraphicNode
to the base node class TVMDNode
(which is used for "unknown" nodes, where RTTI was unable to find the matching class of the dataset, so at least the binary raw data can be read using TVMDNode.RawData
).
So he will change my class as follows:
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;
The problem is that IGraphicNode
is vendor/program-specific and should not be in the API's Database_Kernel.pas
, since GUI and Model/API should be strictly divided.
My wish would be that the interace IGraphicNode
could be added and implemented to the existing TVMDNode
class (which is already a descendant of TInterfacedPersistent
to allow interfaces) in a separate unit. As far as I know, Delphi does not support something like this.
Beside the fact that it is not nice to mix Model and View in one single unit/class, there will be following real-world problem: If the vendor has to change my Database_Kernel.pas
API to extend TVMDNode
with the IGraphicNode
interface, he needs to re-do all his changes, as soon as I release a new version of my API Database_Kernel.pas
.
What should I do? I thought very long about possible solutions possible with Delphi's OOP. A workaround may be nesting TVMDNode's into a container class, which has a secondary RTTI, so after I have found the TVMDNode
class, I could search for a TVMDNodeGUIContainer
class. But this sounds very strangle and like a dirty hack.
PS: This API is an OpenSource/GPL project. I am trying to stay compatible with old generations of Delphi (e.g. 6), since I want to maximize the number of possible users. However, if a solution of the problem above is only possible with the new generation of Delphi languages, I might consider dropping Delphi 6 support for this API.
You can preserve the ability to persist data and implement it through inheritance and still create the correct instances for the ClassGUIDs stored in the tables if you'd apply the factory design pattern.
For each node class there would be one class factory (or just a function pointer) responsible for creation of the correct Delphi class. Class factories may register themselves in the unit initialization section (once per application startup) at the kernel singleton object.
The kernel singleton would then map GUID to correct factory that would in turn call the correct class instance constructor (as shown at http://delphipatterns.blog.com/2011/03/23/abstract-factory)
Packages may be split into separate DLLs and classes implemented in separate units, still inheriting from one base TVMNode class.
The features you now use RTTI for can be supported in descendant classes or in the factory classes easily through some virtual methods.
You might also consider using simpler Data Transfer Objects for saving/loading the TVMNodes and perhaps take some inspiration from an already well perceived Object Relational Mapper or a Object Persistence framework as the problem you are trying to solve seem to me like exactly the problems they are handling (already)
I don't know about good Delphi open source frameworks of this class. But from other languages you can look at Java Hibernate, Microsoft .NET Entity Framework or minimalistic Google Protocol Buffers serializer
这篇关于之后添加一个接口到一个类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!