在 Delphi 中使用接口的优点和缺点是什么?

j91ykkif  于 2023-01-20  发布在  其他
关注(0)|答案(9)|浏览(247)

我已经使用 Delphi 类有一段时间了,但是从来没有真正进入使用接口的领域。我已经读了一些关于它们的东西,但是想学习更多。
我想听听你在 Delphi 中使用接口时遇到的关于编码、性能、可维护性、代码清晰度、层分离以及一般来说你能想到的任何方面的优点和缺点。

qmb5sa22

qmb5sa221#

我现在能想到的就是:
优点:

  • 接口和实现之间的明确分离
  • 减少设备依赖性
  • 多重遗传
  • 参考计数(如果需要,可禁用)

缺点:

  • 类引用和接口引用不能混合(至少在引用计数的情况下)
  • 所有属性都需要getter和setter函数
  • 引用计数不适用于循环引用
  • 调试困难(感谢gabr和warren指出这一点)
luaexgnf

luaexgnf2#

除了这些答案之外,还有一些优势:
1.使用接口来表示行为,并且行为的每个实现都将实现接口。

  1. API发布:接口在发布API时非常有用。你可以发布一个接口而不给出实际的实现。所以你可以自由地进行内部结构的更改而不会给客户端带来任何问题。
sd2nnvve

sd2nnvve3#

我要说的是,没有引用计数的接口在我对 Delphi 的愿望清单上非常高!!!
接口的真实的用途是接口的声明,而不是引用计数的能力!

avkwfej4

avkwfej44#

界面有一些微妙的缺点,我不知道人们在使用它们时是否考虑到了:
1.调试变得更加困难。我在调试器中看到过很多奇怪的困难,在步入接口方法调用时。

  1. Delphi 中的接口带有IUnknown语义,不管你喜欢与否,你都会坚持引用计数是一个受支持的接口。因此,对于Delphi世界中创建的任何接口,你都必须确保正确地处理引用计数,如果你不这样做,你将以泄漏告终。当你想避免引用计数时,你唯一的选择就是重写addref/decref,并且不释放任何东西,但是这也不是没有问题的,我发现接口负载更重的代码库有一些最难发现的访问违规和内存泄漏,这就是,我认为这是因为很难将refcount语义和默认的 Delphi 语义结合起来(所有者释放对象,其他人都不释放,大多数对象在其父对象的整个生命周期内都存在)。
    1.使用接口的糟糕实现会带来一些令人讨厌的代码味道。例如,在定义类的初始具体实现的同一个单元中定义接口,增加了接口的所有权重,而没有真正提供接口用户和实现者之间的适当分离。我知道这不是接口本身的问题,但是对于那些编写基于接口的代码的人来说,这是一个很大的问题。请把你的接口声明放在只有那些接口声明的单元中,避免单元到单元的依赖地狱,因为把你的接口声明放在和你的实现者类相同的单元中。
oymdgrw7

oymdgrw75#

当我希望具有不同祖先的对象提供一个公共服务时,我通常会使用接口,根据我自己的经验,我能想到的最好的例子是一个名为IClipboard的接口:

IClipboard = interface
  function CopyAvailable: Boolean;
  function PasteAvailable(const Value: string): Boolean;
  function CutAvailable: Boolean;
  function SelectAllAvailable: Boolean;
  procedure Copy;
  procedure Paste(const Value: string);
  procedure Cut;
  procedure SelectAll;
end;

我有一堆从标准VCL控件派生而来的自定义控件,它们都实现了这个接口,当剪贴板操作到达我的一个窗体时,它会查看活动控件是否支持这个接口,如果支持,则调度相应的方法。
对于一个非常简单的接口,你可以用of object事件处理器来完成,但是一旦它变得足够复杂,接口就可以很好地工作。事实上,我认为这是一个非常好的类比。使用一个单一of object事件不适合功能的接口。

mrphzbgm

mrphzbgm6#

接口解决了某种问题。主要功能是......好吧,......定义接口。区分定义和实现。
当您要指定或检查类是否支持一组方法时-请使用接口。
你不能用任何其他方式做到这一点。
(If所有的类都继承自同一个基类,那么抽象类将定义接口。但是当你处理不同的类层次结构时,你需要接口来定义你共有的方法。)

dddzy1tm

dddzy1tm7#

关于缺点的额外说明:业绩
我认为许多人过于轻率地忽视了接口的性能损失。(并不是我不喜欢和不使用接口,但是您应该知道您正在进入的是什么)。接口可能是昂贵的,不仅仅是因为_AddRef / _Release命中(即使你只是返回-1),而且属性必须有一个Get方法。类中的大多数属性具有读访问器的直接访问(例如,属性Prop 1:整数读取FProp 1写入SetProp 1)。改变这种直接方式,对函数调用的无惩罚访问可能会大大降低您的速度(特别是当您开始在循环中添加数十个属性调用时)。
例如,使用类的简单循环

for i := 0 to 99 do
begin
  j := (MyClass.Prop1 + MyClass.Prop2 + MyClass.Prop3) / MyClass.Prop4;
  MyClass.Update;
  // do something with j
end;

当类变成一个接口时,从0个函数调用变成400个函数调用。在那个循环中添加更多的属性,它很快就会变得更糟。
你可以用一些技巧来减轻_AddRef / _Release的惩罚(我相信还有其他的技巧,这是我想不起来的):

  • 使用WITH或为临时变量赋值时,每个代码块只会损失一个_AddRef / _Release
  • 始终使用 const 关键字将接口传递到函数中(否则,每次调用该函数时都会出现额外的_AddRef / _Release。
jaql4c8m

jaql4c8m8#

我们必须使用接口的唯一情况(除了COM/ActiveX的东西)是当我们需要多重继承,而接口是获得它的唯一途径。在其他几种情况下,当我们尝试使用接口时,我们遇到了各种各样的问题,主要是引用计数(当对象作为类示例和通过接口访问时)。
因此,我的建议是,只有当你知道你“需要”它们时才使用它们,而不是当你“认为"它能在某些方面使你的生活更轻松时。
更新:正如大卫所提醒的,使用接口,您只能获得接口的多重继承,而不能获得实现的多重继承,但这对我们的需要来说是很好的。

soat7uwm

soat7uwm9#

除了其他已经列出的接口之外,接口的一大优点是聚合它们的能力。
我写了一篇关于这个主题的博客文章前一段时间,可以在这里找到:http://www.nexusdb.com/support/index.php?q=intf-aggregation(t1;dr:你可以有多个对象,每个对象实现一个接口,然后把它们组装成一个集合,在外界看来,这个集合就像是一个实现所有这些接口的单个对象)
您可能还想看看链接到那里的“接口基础”和“高级接口用法和模式”帖子。

相关问题