32

在 Delphi中使用流利的接口有什么好处和坏处?

流畅的接口应该增加可读性,但我有点怀疑是否有一个包含许多链接方法的长 LOC。

有编译器问题吗?
有没有调试问题?
是否有任何运行时/错误处理问题?

Fluent 接口用于例如TStringBuilderTHTMLWriterTGpFluentXMLBuilder


更新:
大卫赫弗南问我关心哪些问题。我已经对此进行了一些思考,总体问题是“明确指定如何完成”与“让编译器决定如何完成”之间的区别。

AFAICS,没有关于编译器实际上如何处理链式方法的文档,也没有任何关于编译器应该如何处理链式方法的规范。

本文中,我们可以了解编译器如何将两个额外的 var 参数添加到声明为函数的方法中,并且标准调用约定将三个参数放在寄存器中,然后将下一个参数放在堆栈中。因此,具有 2 个参数的“流利函数方法”将使用堆栈,而具有 2 个参数的“普通过程方法”仅使用寄存器。

我们还知道,编译器在优化二进制文件方面做了一些神奇的事情(例如,作为函数结果的字符串评估顺序对本地 proc 的引用),但有时会给程序员带来令人惊讶的副作用。

因此,内存/堆栈/寄存器管理更复杂的事实以及编译器可以在无意中产生一些副作用的事实对我来说非常难闻。因此问题。

在我阅读了答案(非常好的答案)之后,我的担忧大大减少了,但我的偏好仍然相同:)

4

5 回答 5

24

每个人都在写消极的问题,所以让我们强调一些积极的问题。错误,唯一的积极问题 - 更少(在某些情况下更少)打字。

我编写 GpFluentXMLBuilder 只是因为我讨厌在创建 XML 文档时输入大量代码。不多也不少。

流畅接口的好处是,如果您讨厌这种习惯用法,则不必以流畅的方式使用它们。它们完全可以以传统方式使用。

编辑:“简短和可读性”观点的观点。

我正在调试一些旧代码并偶然发现:

fdsUnreportedMessages.Add(CreateFluentXml
  .UTF8
  .AddChild('LogEntry')
    .AddChild('Time', Now)
    .AddSibling('Severity', msg.MsgID)
    .AddSibling('Message', msg.MsgData.AsString)
  .AsString);

我立即知道代码的作用。但是,如果代码看起来像这样(而且我并没有声称这甚至可以编译,我只是将它放在一起进行演示):

var
  xmlData: IXMLNode;
  xmlDoc : IXMLDocument;
  xmlKey : IXMLNode;
  xmlRoot: IXMLNode;

  xmlDoc := CreateXMLDoc;
  xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction('xml', 
    'version="1.0" encoding="UTF-8"'));
  xmlRoot := xmlDoc.CreateElement('LogEntry');
  xmlDoc.AppendChild(xmlRoot);
  xmlKey := xmlDoc.CreateElement('Time');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(FormatDateTime(
    'yyyy-mm-dd"T"hh":"mm":"ss.zzz', Now));
  xmlKey.AppendChild(xmlData);
  xmlKey := xmlDoc.CreateElement('Severity');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(IntToStr(msg.MsgID));
  xmlKey.AppendChild(xmlData);
  xmlKey := xmlDoc.CreateElement('Message');
  xmlDoc.AppendChild(xmlKey);
  xmlData := xmlDoc.CreateTextNode(msg.MsgData.AsString);
  xmlKey.AppendChild(xmlData);
  fdsUnreportedMessages.Add(xmlKey.XML);

我需要相当长的时间(和一杯咖啡)才能理解它的作用。

编辑2:

埃里克格兰奇在评论中提出了一个完全正确的观点。实际上,人们会使用一些 XML 包装器而不是直接使用 DOM。例如,使用 OmniXML 包中的OmniXMLUtils,代码将如下所示:

var
  xmlDoc: IXMLDocument;
  xmlLog: IXMLNode;

  xmlDoc := CreateXMLDoc;
  xmlDoc.AppendChild(xmlDoc.CreateProcessingInstruction(
    'xml', 'version="1.0" encoding="UTF-8"'));
  xmlLog := EnsureNode(xmlDoc, 'LogEntry');
  SetNodeTextDateTime(xmlLog, 'Time', Now);
  SetNodeTextInt(xmlLog, 'Severity', msg.MsgID);
  SetNodeText(xmlLog, 'Message', msg.MsgData.AsString);
  fdsUnreportedMessages.Add(XMLSaveToString(xmlDoc));

不过,我更喜欢流利的版本。[而且我从不使用代码格式化程序。]

于 2011-02-24T13:30:55.807 回答
18

编译器问题:

如果您使用接口(而不是对象),则链中的每个调用都会导致引用计数开销,即使始终返回完全相同的接口,编译器也无法知道。因此,您将生成更大的代码,并具有更复杂的堆栈。

调试问题:

调用链被视为一条指令,您不能在中间步骤上单步执行或断点。您也不能在中间步骤评估状态。调试中间步骤的唯一方法是在 asm 视图中进行跟踪。如果相同的方法在 fluent 链中多次发生,调试器中的调用堆栈也将不清楚。

运行时问题:

当使用链的接口(而不是对象)时,您必须为引用计数开销以及更复杂的异常帧付出代价。您不能在链中尝试..finally 构造,因此不能保证关闭在流利的链中打开的内容 fi

调试/错误日志记录问题:

异常及其堆栈跟踪会将链视为一条指令,因此如果您在 .DoSomething 中崩溃,并且调用链有多个 .DoSomething 调用,您将不知道是哪个导致了问题。

代码格式问题:

AFAICT 现有的代码格式化程序都不能正确布局流畅的调用链,因此只有手动格式化才能保持流畅的调用链可读。如果运行自动格式化程序,它通常会将链变成可读性混乱。

于 2011-02-24T12:15:03.840 回答
7

Are there any compiler issues?

不。

Are there any debugging issues?

是的。由于所有链接的方法调用都被视为一个表达式,即使您在链接的 Wikipedia 示例中将它们写在多行上,调试时也会出现问题,因为您无法单步执行它们。

Are there any runtime/error handling issues?

已编辑这是我编写的一个测试控制台应用程序,用于测试使用 Fluent 接口的实际运行时开销。我为每次迭代分配了 6 个属性(实际上每个相同的 2 个值 3 次)。结论是:

  • 使用接口:运行时间增加 70%,取决于设置的属性数量。仅设置两个属性,开销较小。
  • 使用对象:使用流畅的接口更快
  • 没有测试记录。它不能很好地处理记录!

我个人不介意那些“流畅的界面”。以前从未听说过这个名字,但我一直在使用它们,尤其是在从代码中填充列表的代码中。(有点像您发布的 XML 示例)。我不认为它们难以阅读,特别是如果一个人熟悉这种编码并且方法名称是有意义的。至于一长行代码,看维基百科的例子:你不需要把它全部放在一行代码上。

我清楚地记得使用它们Turbo Pascal来初始化屏幕,这可能是我不介意它们并且有时也使用它们的原因。不幸的是,谷歌让我失望了,我找不到旧 TP 代码的任何代码示例。

于 2011-02-24T11:51:01.127 回答
3

我会质疑使用“流利的界面”的好处。

据我所知,重点是让您避免必须声明一个变量。因此,可怕的 With 声明带来了同样的好处,但有一组不同的问题(请参阅其他答案)

老实说,我从不理解使用 With 语句的动机,也不理解使用流畅接口的动机。我的意思是定义一个变量有那么难吗?所有这些胡说八道只是为了让懒惰。

我会争辩说,与其增加它的可读性,乍一看它似乎通过减少输入/阅读来实现,它实际上混淆了它。

所以,我再次问你为什么要首先使用流畅的接口?

它是由 Martin Fowler 创造的,所以它一定很酷吧?不,我不买它。

于 2011-02-24T12:35:16.987 回答
2

这是一种一次写入从不读取的表示法,如果不阅读所有相关方法的文档,就不容易理解。此外,这种表示法与 Delphi 和 C# 属性不兼容 - 如果您需要设置属性,则需要回滚到使用通用表示法,因为您无法链接属性分配。

于 2011-02-24T11:29:31.490 回答