这是接口的一个特例,其中一个类实现了同一接口的多个版本,即.类似以下内容
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;
Run Code Online (Sandbox Code Playgroud)
当然,在实际案例中大约有十几个ISpecialXxxx.
非常重要的是只需要一个实例,即.我想避免创建适配器或虚拟实例只是为了推迟ISpecialXxxx实现,因为之前设计的唯一目的正是让单个实例处理许多出色的接口(即TMyClass的RefCount可以达到千分之一秒) ).
现在的问题是GetTheRightOne()返回一个IBase,但在某些时候我想检查是否可以将IBase强制转换为ISub.
有没有办法用上面的申报表格来做?
一种方法是添加一个
function GetSub : ISub;
Run Code Online (Sandbox Code Playgroud)
到IBase,但这确实使设计变得更重,因为它必须为每个ISpecialXxxx实现,并且对于ISpecialXxxx"继承"是多余的,所以我正在寻找更优雅的解决方案(假设它存在) .
(我有其他"膨胀"解决方案,所以我真的想强调我正在寻找一个非膨胀的解决方案)
编辑:更多细节
edit2:如果你想要血淋淋的细节
检查https://code.google.com/p/dwscript/source/browse/trunk/Source/dwsJSONConnector.pas(r2492),TdwsJSONConnectorType类和IJSONLow界面,目标是从中检测到IConnectorFastCall它作为IConnectorCall传递,因此能够调用LowFastCall而不是LowCall.
检测必须在TConnectorCallExpr.AssignConnectorSym第294行进行,其中当前有一个QueryInterface.
请注意,QueryInterface适用于TdwsJSONIndexReadCall和TdwsJSONIndexWriteCall,因为它们从不同的类和实例实现IConnectorCall和IConnectorFastCall.但这就是我想要避免的.
当然,理想情况下,目标是将所有内容折回到ConnectorType类(单个类,单个实例)中,并且对于每个接口,特定的ConnectorType类应该可以自由地实现IConnectorCall或IConnectorFastCall.
要查看接口的实现者是否实现了您可以使用的另一个接口Supports或QueryInterface,如下面的伪代码:
var
Base: IBase;
Sub: ISub;
begin
Base := X.GetTheRightOne(Params);
if Supports(Base, ISub, Sub) then
Sub.Bar;
end;
Run Code Online (Sandbox Code Playgroud)
编辑:要使上述工作,您需要将IID添加到接口的声明中.
小智 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.
Run Code Online (Sandbox Code Playgroud)
这输出
BaseFoo
---
SubFoo
SubBar
...
Run Code Online (Sandbox Code Playgroud)
我们想要的.
这是我目前的"最佳"解决方案:
我放弃了方法解析子句,并转移到绑定到主类的虚拟类,并且只实例化一次.
这样,可以使用GetInterface和Supports,因为ISub再次显式.
但是,这引发了循环引用的问题:主类需要引用特殊内容(如果只是在GetTheRightOne()中返回它们),并且特殊内容需要引用主类(访问存储在那里的参数或重定向到方法)主要课程).
主类和特殊区都是引用计数接口,当然,使用上下文是多线程的,因此通常的弱引用方案将引入对全局锁的需求.
但是鉴于特殊内容是仅用于主类的接口解析的虚拟类,我们可以覆盖它们的_AddRef和_Release以使引用计数集中在主类上(即_AddRef和_Release只是重定向到主类的_AddRef&_Release ,并且不再保持自己的引用计数).