实现多个接口的类的清晰性(代理的替代方案):

7
假设我们有以下内容:
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 - 感谢您的精彩编辑 - 我在发布时非常匆忙。 - Vector
4个回答

16

我认为,在不使用注释的情况下,你唯一可以做的就是添加方法解析子句

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个接口,我希望代码能够自我定义它正在实现哪个接口。方法解析并不是为此设计的——它只是避免名称冲突的一种方式。我想我必须要么忍受这种混乱,要么“勇敢地”使用委托... - Vector
你所寻找的并不存在,我相信。 - David Heffernan
1
实现 ISomething 对人类的好处应该作为注释,就是这样。 - Warren P
当你必须消除两个方法名称冲突时,方法解析子句确实非常好用(当然,在这种情况下,我更喜欢仅通过委托来消除歧义)。 - Warren P

11
在您发表的这个案例中,我也更喜欢评论(接口名称就像NGLN所说的那样),但我想解释一下为什么implements关键字在某些其他情况下可能是最佳解决方案,而不是在您的简单示例中只有一个方法的平凡情况下。
我知道您说您了解Implements;但对于以后会看到此内容的人来说,我想记录下它何时有用,请耐心等待。在某些情况下,即使需要更多的类,它也值得额外的工作。
因此,我使用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个函数。我继承了一个大碗意大利面要解决 - 我本来已经开始实现委托了 - 今天下午意识到我真的必须这样做 - 但我有时间限制,而且我已经有很多新代码,所以我正在寻找一些快速的东西,可以用来强制执行一些组织并得到一些“出门”的东西。所以,我会给你答案的信用,因为你花时间教育公众,并在明天上午开始委托... - Vector
@mikey 或许你可以编辑问题,删除那部分内容,即你不想使用委托的部分! - David Heffernan
他没有使用委托,但如果他有2/3个接口,每个接口都有15/20个函数,那么他应该使用委托。(或许在问题中加入这个信息会更好)。 - Warren P
我认为标题被改变了。我本来会把标题写成“我不想使用委托,但我希望委托发生...我该怎么办?” :-) - Warren P

2

虽然您明确要求不涉及评论的答案,但我可以说,在Delphi VCL之外,使用注释更常见的解决方案,具体如下:

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

3
只有人类需要帮助,因此只有注释应该改变。代码是给计算机用的(你也可以读懂),但如果你的大脑需要信息,注释是理想的选择。 - Warren P
1
嗯,我会想知道为什么实现是有关的。在接口中,看到哪些类的部分存在以满足接口(合同)是可以理解的,但除此之外,实现的组织方式对于任何关于接口等方法存在的问题都是完全无关紧要的,更高效的导航组织成为关键。例如,我将所有的getter/setter方法放在构造函数/析构函数之后,但在任何“真正”的方法之前,并且简单地按字母顺序排列... [..cont.] - Deltics
同意 - 请看我对沃伦回答的评论。 - Vector
@Warren - “但是它不应该包含非执行注释,就好像它们做了什么一样。那将是邪恶的。” 我们在这里讨论 Delphi 的属性可能会带来很多争议(即使它们确实“执行”)- 大多数语言似乎在没有使用它们的情况下完全可以胜任,并且如果您使用接口,它们大多无用 - 您可以使用没有“属性”的访问方法,这仅相当于“语法糖”... - Vector
Delphi就是Delphi,没有必要把它的设计称作“糖果”,因为它本来就不是。 - Warren P
显示剩余7条评论

2

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;

请参考David上面的回答和相关评论。谢谢。 - Vector
抱歉再次提起这个旧帖子,但是否有人知道为什么会有这种新语法,与 property [...] implements 相比?为什么不选择 procedure DoSomethingInterestingFirst implements IMyFirstInterface.DoSomethingInteresting; 呢? - Thijs van Dien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接