Delphi中的流畅界面

Jør*_*eit 31 delphi fluent-interface

在Delphi 中使用流畅的接口有什么优缺点?

流畅的接口应该会增加可读性,但我有点怀疑有一个包含大量链接方法的长LOC.

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

Fluent接口用于例如TStringBuilder,THTMLWriterTGpFluentXMLBuilder.


更新:
David Heffernan询问我关注哪些问题.我已经考虑过这个问题了,整体问题是"明确指定它是如何完成"与"让编译器决定如何完成"之间的区别.

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

本文中,我们可以阅读有关编译器如何向声明为函数的方法添加两个额外的var参数,以及标准调用约定在寄存器中放置三个参数以及在堆栈中放置下一个参数的情况.因此,具有2个参数的"流畅函数方法"将使用该堆栈,而具有2个参数的"普通过程方法"仅使用该寄存器.

我们也知道编译器在优化二进制文件方面做了一些魔术(例如字符串作为函数结果,评估顺序,参考本地proc),但有时会给程序员带来惊人的副作用.

因此,内存/堆栈/寄存器管理更复杂以及编译器可能会产生一些无意识的副作用,这对我来说非常臭.因此问题.

在我阅读了答案(非常好的答案)后,我的担忧大打折扣,但我的偏好仍然是相同的:)

gab*_*abr 23

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

我编写GpFluentXMLBuilder只是因为我讨厌在创建XML文档时输入大量代码.没有更多,也没有更少.

流畅的界面的好处是,如果你讨厌成语,你不必以流利的方式使用它们.它们以传统方式完全可用.

编辑:"短缺和可读性"观点.

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

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

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

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);
Run Code Online (Sandbox Code Playgroud)

我需要一些时间(和一杯咖啡)来了解它的作用.

EDIT2:

Eric Grange在评论中提出了一个非常有效的观点.实际上,人们会使用一些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));
Run Code Online (Sandbox Code Playgroud)

不过,我更喜欢流利的版本.[我从来没有使用过代码格式化程序.]

  • @JørnE.Angeltveit - *在XML*的情况下,流畅的接口实际上更容易从代码中看到预期的结果文档.我不能说使用这种方法创建具有复杂结构的文档是多么容易.由于您可以清楚地"看到"输出,因此更容易维护 - 例如,在特定位置添加新元素. (7认同)
  • @gabr,没有投票给你的答案,因为技术上它是正确的(更少打字).但是,通过使用较差(使用MyQuery,MyButton,MyTreeView,MyOtherThing做...),您可以减少打字.它基本上使得代码极难调试和维护,即使您节省了大量的击键.我认为流畅的界面可能会遇到同样的问题. (2认同)
  • +1.不要忘记保存的局部变量.听起来不是很多,但我在运行时使用不同的类填充列表时大多使用Fluent接口.如果没有流畅的样式接口,您最终会为每个需要使用的类类型使用一个变量,即使您只需要为一个属性设置值. (2认同)
  • @gabr,我将再次查看GpFluentXMLBuilder.我第一次看的时候并没有这么想,但你可能是对的(@ Leonardo的观点似乎也很好).谢谢. (2认同)

Eri*_*nge 19

编译问题:

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

调试问题:

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

运行时问题:

当为链(而不是对象)使用接口时,您必须为引用计数开销以及更复杂的异常帧付费.你不能尝试在链中进行最终构造,因此无法保证关闭在流畅链中打开的内容

调试/错误记录问题:

异常及其堆栈跟踪将链视为单个指令,因此如果您在.DoSomething中崩溃,并且调用链中有几个.DoSomething调用,您将不知道是哪个引起了该问题.

代码格式问题:

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

  • 您可以单步执行表达式,如果您 - 在每个程序结束时 - 您使用F11退出.这是非常耗时的,所以不推荐. (2认同)

Cos*_*und 7

Are there any compiler issues?

没有.

Are there any debugging issues?

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

Are there any runtime/error handling issues?

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

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

我个人不介意那些"流畅的界面".从来没有听说过这个名字,但我一直在使用它们,尤其是在代码中填充列表的代码中.(有点像你发布的XML示例).我不认为它们很难阅读,特别是如果你熟悉这种编码并且方法名称是有意义的.至于一长串代码,请查看Wikipedia示例:您不需要将其全部放在一行代码上.

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