【问题标题】:Does interface delegation of an inherited interface require a wrapper class?继承接口的接口委托是否需要包装类?
【发布时间】:2015-11-12 00:56:34
【问题描述】:

Delphi 允许使用implements 关键字进行接口委托。

例如

IIndep1 = interface
  function foo2: integer;
end;

IIndep2 = interface
  function goo2: integer;
end;

TIndep1And2 = class(TInterfacedObject, IIndep1, IIndep2)
private
  FNested : IIndep1; //e.g. passed via constructor or internally created (not shown here)
public
   Constructor Create(AIndep1: IIndep1);
  function goo2: integer;
   property AsIndep1 : IIndep1 read FNested implements IIndep1;
end;

这很好用,但不适用于继承的接口。 (错误信息“缺少接口方法ILev1.foo的实现”)

ILev1 = interface
  function foo: Integer;
end;

ILev2 = interface(ILev1)
  function goo: Integer;
end;

TLev2Fails = class(TInterfacedObject, ILev1, ILev2) //Also fails with ILev2 alone (Error: "ILev1 not mentioned in interface list")
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   function goo: Integer;
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

解决方法是添加一个额外的祖先类

TLev1Wrapper = class(TInterfacedObject, ILev1)
private
  FNested : ILev1; //passed via constructor or internally created 
public
   Constructor Create(AILev1: ILev1);
   property AsLev1 : ILev1 read FNested implements ILev1;
end;

TLev2Works = class(TLev1Wrapper, ILev2)
public
  function goo: Integer;
end;

有没有办法避免包装类的祖先?

[编辑] 只是关于接口委托的说明,使用implements 的目的是避免直接满足接口,而是将该要求传递给聚合或组合成员。提供完整的界面并手动委派给组合成员会破坏使用implements 引导界面所获得的好处。事实上,在这种情况下,implements 关键字和属性可能会被删除。

【问题讨论】:

  • 我认为这一定是编译器错误。我查看了文档,没有看到任何与此限制相关的内容。
  • 感谢@RudyVeldthuis 指出问题在于无法绑定的 ILev2.foo。
  • @Graymatter 感谢以下 cmets 中的链接。我来回讨论这是否是一个错误。很可能是一个

标签: delphi interface delphi-xe2 delphi-xe4 delphi-xe8


【解决方案1】:

有没有办法避免包装类的祖先?

不,继承的接口必须绑定到祖先类。由于here 解释的原因,Delphi 不会隐式绑定继承的接口。此外,它不能绑定到当前类,除非它手动将所有调用委托给组合项。

我们只剩下一个选择。一个祖先包装类,我们可以绑定ILev1

【讨论】:

    【解决方案2】:

    这看起来编译器正在尝试强制执行the expectations (read: requirements) of IUnknown.QueryInterface

    对于任何一个对象,对任何一个对象上的 IUnknown 接口的特定查询 对象的接口必须始终返回相同的指针值。

    如果您能够在自己实现派生接口的同时委托基接口的实现,那么:

    obj := TLev2Fails.Create(otherLev1);  // Assuming your class could compile
    
    lev1 := obj as ILev1;     // yields reference to otherLev1 implementor
    lev2 := obj as ILev2;     // yields reference to TLev2Fails instance
    
    unk1 := lev1 as IUnknown;   // returns IUnknown of otherLev1 implementor
    unk2 := lev2 as IUnknown;   // returns IUnknown of obj TLev2fails instance
    

    如果您的嵌套对象被正确实现为 TAggregatedObject 派生类,则情况并非如此,但编译器无法知道是否是这种情况,更不用说强制执行它了它只是要求,如果您实现派生接口,那么您还必须直接实现接口本身继承的任何接口。

    这种情况下的编译器错误不是很有帮助,尽管它可以被解读为告诉你什么你需要做什么,而不是为什么你需要在这种情况下执行此操作,这对于编译器错误来说并不少见。

    如果您希望在这种情况下进行委托,那么您必须“手动委托”。

    虽然这失去了implements 设施的好处,但它至少保留了重复使用的好处,只是不太方便。

    注意:即使您的委托实现基于 TAggregatedObject,编译器仍然无法确定这些实现细节是否满足QueryInterface,所以您仍然会收到此错误(即使使用委托接口的类引用)。

    您仍然必须手动委派。

    说了这么多,我目前看不出这对于没有继承关系的接口有什么不同,但很可能这是有效的,我只是没有完成所有必要的工作思想实验”向自己证明。 :)

    可能是编译器只是在认为可以/应该这样做的情况下格外谨慎,而文档根本没有提到implements的这种限制。

    或者它可能是编译器中的一个错误,尽管我认为该行为有足够明显的原因,并且该行为本身是如此完善和一致(在 Delphi 7 中重现了完全相同的行为)以至于相关的遗漏文档是更可能的解释。

    【讨论】:

    • 您可以使用TAggregatedObject 发布解决方案吗?查询委托接口与直接实现的接口将在不同的对象处结束,这是意料之中的。您为 TIndep1And2 更改的示例将产生与两个不同对象的 IUnknown 相似的结果。继承的情况下问题更严重。您的示例可能有lev2 as ILev1,那么您将有两个对象上的ILev1。也就是说,如果在一种情况下没有强制执行,那为什么是另一种呢?
    • 使用 TAggregatedObject 仍然不利于 implements 的使用:您仍然需要手动委托。 TAggegratedObject 只负责反映“容器”对象的 IUnknown。至于为什么在一种情况下而不是另一种情况下:编译器不能确定独立接口不应该独立实现,因为它可以是继承的接口不应该。这解释了行为差异的潜在原因,但不会改变编译器在任何一种情况下都可能出错的事实。
    • 我同意你所说的“我看不出这与没有继承关系的接口有什么不同”的部分。我经常做的事情。我删除了我的评论,但会添加回来。这一定是编译器错误。
    • 你有一个班级有2个implements的情况呢?那不会遇到同样的问题。如果您查看我的这个问题和 David 的回答 (stackoverflow.com/questions/22649433/…),我认为 Delphi 看到的“对象”会根据正在访问的接口以及访问方式(也取决于 Delphi 的哪个版本)而变化正在使用)。因此,虽然我们在代码中看到相同的对象,但接口的对象是被引用计数的对象,这就是 IUnknown 使用的对象。
    • @graymatter - 所涉及的任何对象都完全依赖于实现,编译器无法做任何事情来影响它。没有任何编译器魔法可以改变您的 QueryInterface 实现产生的结果。我不知道您为什么认为这取决于所使用的 Delphi 版本。据我所知,从 Delphi 7 到 Delphi XE8 的行为是一致的。显然这是一个“编译器错误”,但这并不一定意味着它是一个错误。根据编译器规则只是一个错误,就像将整数分配给字符串会导致编译器错误一样。 :)
    猜你喜欢
    • 2017-07-18
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    • 1970-01-01
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    相关资源
    最近更新 更多