【问题标题】:Clarity in classes implementing multiple interfaces (alternative to delegation):实现多个接口的类的清晰性(替代委托):
【发布时间】:2011-10-13 18:22:55
【问题描述】:

假设我们有以下内容:

IFirst = Interface(IUnknown)    
  function GetStuff: Integer;
end;

ISecond = Interface(IUnknown)
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)    
private 
  function GetStuff: Integer;        //implementation of IFirst
  function GetOtherStuff: Integer;   //implementation of ISecond;
end;

我从不喜欢在 TInterfacedObject 中似乎无法区分哪些方法实现哪些接口的事实。我错过了什么吗?有谁知道一种构造代码的方法来做到这一点?指定GetStuff 是IFirst 的实现,GetOtherStuff 是ISecond 的实现? (“发表评论”不是我正在寻找的答案......)

我知道我可以使用“implements”指令在 TFirstSecond 中为每个接口定义属性,并将实现委托给 TFirstSecond 中包含的实例,从而隔离所有内容。但我想要一个捷径...

【问题讨论】:

  • @daemon_x - 感谢您的精彩编辑 - 我发帖时非常着急。

标签: delphi interface implementation delegation


【解决方案1】:

我想不使用 cmets 你唯一能做的就是添加method resolution clauses:

IFirst = interface
  function GetStuff: Integer;
end;

ISecond = interface
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  function GetStuff: Integer;
  function GetOtherStuff: Integer;
public
  function IFirst.GetStuff = GetStuff;
  function ISecond.GetOtherStuff = GetOtherStuff;
end;

我不认为这真的增加了很多,我个人认为这比没有方法解析子句更糟糕。

【讨论】:

  • 我忘记了那个结构,你提醒了我 - 虽然我不认为它那么糟糕(你的问题是什么?冗余?)我认为它并不能真正解决问题,因为我关心的主要是代码的实现部分 - 如果我有 10 个复杂方法,每个方法用于 2 个接口,我希望代码自行定义它实现的接口。 方法解析确实不是为此而设计的-这只是避免名称冲突的一种方式。我想我要么忍受混乱,要么“站起来”并使用委托……
  • 我相信你要找的东西不存在
  • {implementing ISomething} 为了人类的利益真的应该是一个评论,就是这样。
  • 方法解析子句然而,当您必须消除两个否则会发生冲突的方法名称时,它确实非常棒(当然,在这种情况下,我希望 100% 仅通过委托消除歧义)。
【解决方案2】:

虽然您特别要求不涉及 cmets 的答案,但我可以说使用 cmets 不仅仅是 Delphi VCL 的常见解决方案,准确地说如下:

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  { IFirst }
  function GetStuff: Integer;
private
  { ISecond }
  function GetOtherStuff: Integer;
end;

【讨论】:

  • 只有人类需要帮助,因此,只有 cmets 应该改变。代码适用于计算机(您也可以阅读),但如果您的大脑需要这些信息,那么 cmets 是理想的选择。+
  • 如果只是接口部分,那么 cmets 确实足够了——正如我在 cmets 中向 David 解释的那样,我更关心的是实现。既然如此,似乎共识是授权是唯一的方法。
  • 嗯,我想知道为什么实施完全值得关注。在接口中,查看类的哪些部分是为了满足接口(契约)是可以理解的,但除此之外,实现的组织方式与接口有哪些方法等问题以及组织更多的问题完全无关高效导航成为关键。例如我将所有 getter/setter 方法放在构造函数/析构函数之后,但在任何“真实”方法之前,并且只应用字母顺序... [..cont.]
  • [..cont..] 如果我需要在接口和实现部分之间导航,我总是使用 ctrl-up/down,因此使用一个作为“索引”并具体反映另一个的组织是不必要的。如果我在使用方法“Foo”并且想要使用我知道存在的方法“Bar”,我知道它出现在“Foo”之前(按字母顺序)。同时在接口中我可以证明 Foo 是一个接口契约的一部分,而 Bar 是另一个接口契约的一部分,并且提供 Bar 的接口可能在 Foo 之前。只是一个例子。
  • 同意 - 请参阅我对沃伦的回答。
【解决方案3】:

在您发布的情况下,我也更喜欢 cmets(接口名称正如 NGLN 所说),但我想解释一下为什么 implements 关键字在其他一些情况下可能是最佳解决方案,只是不是在琐碎的情况下,每个接口只有一个方法,就像在您的琐碎示例中一样。

我知道你说过你知道工具;但是对于没有看过它的人,我想记录它什么时候有用,请多多包涵。在某些情况下,增加课程的所有额外工作甚至是值得的。

因此,我不会将实现用作捷径(如您所见,它更长!),而是仅当每个接口涉及要实现的 100 种方法时,并且最终的设计具有更少的耦合,并且只有更好的内聚性和可读性。

所以这是一个公认的愚蠢的例子,但如果 IFirst 和 ISecond 各有 100 个方法,那么它可能是一个巨大的飞跃......

type
IFirst = interface
  function GetStuff: Integer;
end;

ISecond = interface
  function GetOtherStuff: Integer;
end;

TFirstLogic = class(TInterfacedObject, IFirst)
  function GetStuff: Integer;

end;

TSecondLogic = class(TInterfacedObject, ISecond)
  function GetOtherStuff: Integer;
end;

TFirstSecond = class(TInterfacedObject, IFirst, ISecond)
private
  FFirst:TFirstLogic;
  FSecond:TSecondLogic;
protected


  property First:TFirstLogic read FFirst implements IFirst;
  property Second:TSecondLogic read FSecond implements ISecond;
public
  constructor Create; // don't forget to create and free FFirst/FSecond.
  destructor Destroy; override; // don't forget to create and free FFirst/FSecond.


end;

你可以说 Implements 是我们可以做“部分类”的唯一方法,或者至少创建一个复合类来实现一堆接口,并拥有一堆子属性(它们是受保护的,甚至可能是私有的)用来做“实现”委托。如果您将其他所有内容从包含聚合类的单元中移出,那么您可以拥有一个非常干净的设计。

【讨论】:

  • 有 2 个或可能 3 个接口,每个接口有 15/20 个函数。我继承了一大碗意大利面来解开​​ - 我本来已经开始实施委托 - 今天下午意识到我真的必须这样做 - 但我有时间限制而且我已经有很多新代码,所以我一直在寻找一些快速的东西,我可以用它来强制执行一些组织并“走出门外”。所以,我会感谢你的答案,因为你花时间教育公众并在明天上午开始授权......
  • @mikey 也许您可以编辑问题以删除表示您不想使用委托的位!
  • 好吧,他没有,但他应该使用委托,如果他有 2/3 个接口,每个接口有 15/20 个函数。 (也许把它放在问题中)。
  • 我认为标题已更改。我会设置标题“我不想使用委托,但我希望委托发生......我应该怎么做?” :-)
【解决方案4】:

D2006(但也可能是更早的版本,D2006 是我目前可用的最早的版本),支持将接口方法“映射”到特定的类函数。这消除了像使用 implements 关键字那样拥有属性的必要性。

当两个接口包含相同的方法签名但需要不同的实现时,使用接口映射也应该是一种消除歧义的方法。

例子:

IMyFirstInterface = interface(IInterface)
  procedure DoSomethingInteresting;
  procedure DoSomethingElse;
end;

IMySecondInterface = interface(IInterface)
  procedure DoSomethingInteresting;
end;

TMyCombinedObject = class(TInterfacedObject, IMyFirstInterface, IMySecondInterface)
private
  // Interface mappings 
  procedure IMyFirstInterface.DoSomethingInteresting = DoSomethingInterestingFirst;
  procedure IMySecondInterface.DoSomethingInteresting = DoSomethingInterestingSecond;
protected
  procedure DoSomethingInterestingFirst;
  procedure DoSomethingInterestingSecond;
  procedure DoSomethingElse;
end;

如果你有很多接口或方法,缺点是:重复。但是,正如您在示例中看到的那样,您不需要为接口中的所有方法指定映射。

出于文档目的,您可以将映射直接与实际的方法声明放在一起,这样它们就不太可能不同步(或者更确切地说,映射会错过新方法,因为您不需要声明每个接口方法的映射):

TMyCombinedObject = class(TInterfacedObject, IMyFirstInterface, IMySecondInterface)
protected
  procedure IMyFirstInterface.DoSomethingInteresting = DoSomethingInterestingFirst;
  procedure DoSomethingInterestingFirst;

  procedure IMySecondInterface.DoSomethingInteresting = DoSomethingInterestingSecond;
  procedure DoSomethingInterestingSecond;

  procedure DoSomethingElse;
end;

【讨论】:

  • 见上面大卫的回答和随附的 cmets。 Tnx
  • 很抱歉提出这个旧线程,但有没有人知道为什么有这个新语法与property [...] implements 相比?为什么没有选择procedure DoSomethingInterestingFirst implements IMyFirstInterface.DoSomethingInteresting;
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-31
  • 1970-01-01
  • 2012-06-25
  • 1970-01-01
  • 2020-11-10
  • 2021-02-20
相关资源
最近更新 更多