这是接口的一个特例,其中一个类实现了同一接口的多个版本,即。类似于以下内容
IBase = interface
procedure Foo;
end;
ISub = interface (IBase)
procedure Bar;
end;
ISpecialBase = interface (IBase) end;
ISpecialSub = interface (ISub) end;
TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)
procedure SpecialFoo1;
procedure SpecialFoo2;
procedure SpecialBar;
procedure ISpecialBase.Foo = SpecialFoo1;
procedure ISpecialSub.Foo = SpecialFoo2;
procedure ISpecialSub.Bar = SpecialBar;
function GetTheRightOne(parameters) : IBase;
end;
...
function TMyClass.GetTheRightOne(parameters) : IBase;
begin
if (something complex depending on parameters) then
Result := ISpecialBase(Self)
else Result := ISpecialSub(Self)
end;
当然,在实际案例中大约有十几个ISpecialXxxx。
非常需要只有一个实例,即。我想避免创建适配器或虚拟实例只是为了推迟ISpecialXxxx实现,因为前一个设计的唯一目的正是让一个实例处理许多出色的接口(即TMyClass的RefCount可以达到千分之一秒) )。
现在问题是GetTheRightOne()返回一个IBase,但在某些时候我想检查是否可以将IBase强制转换为ISub。
有没有办法用上面的申报表格来做?
一种方法是添加
function GetSub : ISub;
到IBase,但这确实使设计变得更重,因为它必须为每个ISpecialXxxx实现,并且对于ISpecialXxxx“继承”将是多余的,所以我正在寻找更优雅的解决方案(假设它存在)。
(我有其他“膨胀”解决方案,所以我真的想强调我正在寻找一个不膨胀的解决方案)
编辑:更多细节
edit2:如果你想要血腥细节
检查https://code.google.com/p/dwscript/source/browse/trunk/Source/dwsJSONConnector.pas(r2492),TdwsJSONConnectorType类和IJSONLow接口,目标是在IConnectorCall作为IConnectorCall传递时从中检测到IConnectorFastCall,从而能够调用LowFastCall而不是LowCall
检测必须在TConnectorCallExpr.AssignConnectorSym第294行进行,其中当前有一个QueryInterface。
请注意,QueryInterface适用于TdwsJSONIndexReadCall& TdwsJSONIndexWriteCall,因为它们实现了IConnectorCall&来自不同类的IConnectorFastCall&实例。但这就是我想避免的。
理想情况下,目标是将所有内容折回到ConnectorType类(单个类,单个实例)中,对于每个接口,特定的ConnectorType类应该可以自由地实现IConnectorCall或IConnectorFastCall。
答案 0 :(得分:7)
要查看接口的实现者是否实现了另一个接口,您可以使用Supports
或QueryInterface,如下面的伪代码所示:
var
Base: IBase;
Sub: ISub;
begin
Base := X.GetTheRightOne(Params);
if Supports(Base, ISub, Sub) then
Sub.Bar;
end;
编辑:要使上述工作正常,您需要将IID添加到接口的声明中。
答案 1 :(得分:6)
一种hackish方式依赖于编译器如何存储接口VTable数据。编译器为对象实现的每个接口存储单独的VTable。在每个VTable之后,它存储对象实现的接口数量。
因此我们可以使用它来确定我们是否获得了祖先接口的VTable,或者是后代的VTable。
至少这是它在XE3和XE5中的工作方式,我必须承认,在谈到接口的实现方式时,我有点像n00b。
除了依赖于实现细节之外,还有一个缺点是,如果向IBase接口添加方法,则必须保持GetSub功能同步。此外,如果您有两个不同的,不相关的ISub,则此代码无法检测到您获得的内容。你也许能够破解它,但我宁愿不去那里......
{$APPTYPE CONSOLE}
uses
System.SysUtils;
type
IBase = interface
procedure Foo;
end;
ISub = interface (IBase)
procedure Bar;
end;
ISpecialBase = interface (IBase)
end;
ISpecialSub = interface (ISub)
end;
TMyClass = class(TInterfacedObject, ISpecialBase, ISpecialSub)
procedure SpecialFoo1;
procedure SpecialFoo2;
procedure SpecialBar;
procedure ISpecialBase.Foo = SpecialFoo1;
procedure ISpecialSub.Foo = SpecialFoo2;
procedure ISpecialSub.Bar = SpecialBar;
function GetTheRightOne(const Param: boolean) : IBase;
end;
{ TMyClass }
function TMyClass.GetTheRightOne(const Param: boolean): IBase;
begin
if Param then
Result := ISpecialBase(Self)
else
Result := ISpecialSub(Self);
end;
procedure TMyClass.SpecialBar;
begin
WriteLn('SubBar');
end;
procedure TMyClass.SpecialFoo1;
begin
WriteLn('BaseFoo');
end;
procedure TMyClass.SpecialFoo2;
begin
WriteLn('SubFoo');
end;
function GetSub(const Intf: IInterface): ISub;
type
PPVtable = ^PVtable;
PVtable = ^TVtable;
TVtable = array[0..MaxInt div SizeOf(Pointer) - 1] of Pointer;
var
intfVTable: PPVtable;
caddr: NativeUInt;
begin
result := nil;
intfVTable := PPVTable(Intf);
// 3 is offset to user methods
// +0 = first user method, +1 = second user method etc
// get the "address" of the first method in ISub
caddr := NativeUInt(intfVTable^[3+1]);
// compiler stores number of interface entries the
// implementing object implements right after the interface vtable
// so if we get a low number here, it means Intf is the IBase interface
// and not the ISub
if caddr > $100 then
result := ISub(Intf);
end;
procedure CallIt(const b: IBase);
var
s: ISub;
begin
b.Foo;
s := GetSub(b);
if Assigned(s) then
s.Bar;
end;
var
c: TMyClass;
b: IBase;
begin
try
c := TMyClass.Create;
b := c.GetTheRightOne(True);
CallIt(b);
WriteLn('---');
b := c.GetTheRightOne(False);
CallIt(b);
WriteLn('...');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn;
end.
此输出
BaseFoo
---
SubFoo
SubBar
...
我们想要的。
答案 2 :(得分:5)
这是我目前的“最佳”解决方案:
我放弃了方法解析子句,并转移到绑定到主类的虚拟类,并且只实例化了一次。
这样,GetInterface&可以使用支持,因为ISub再次显式。
但是,这引发了循环引用的问题:主类需要引用特殊内容(如果只是在GetTheRightOne()中返回它们),并且特殊内容需要引用主类(访问存储在那里的参数或重定向)到主类的方法)。
主类和特殊元素都是引用计数接口,当然,使用上下文是多线程的,因此通常的弱引用方案会引入对全局锁的需求。
然而,鉴于特殊内容是仅用于主类的接口解析的虚拟类,我们可以覆盖它们的_AddRef& _Release让引用计数集中在主类上(即_AddRef& _Release只是重定向到主类的_AddRef& _Release,并且不再保持自己的引用计数。)
答案 3 :(得分:3)
接口继承不遵循与类继承相同的原则。因此,为了测试IBase是否支持ISub,实现类需要显式声明ISub:
TMyClass = class(TInterfacedObject, ISub, ISpecialBase, ISpecialSub)
查询接口不会检查继承的接口。 AFAIR当在Delphi 2(?)中引入的接口时,其中一个编译器人曾经注意到接口继承只不过是语法糖。