Fluent interface in Delphi

ⅰ亾dé卋堺 提交于 2019-12-02 15:49:25

Compiler issues:

If you're using interfaces (rather than objects), each call in the chain will result in a reference count overhead, even if the exact same interface is returned all the time, the compiler has no way of knowing it. You'll thus generate a larger code, with a more complex stack.

Debugging issues:

The call chain being seen as a single instruction, you can't step or breakpoint on the intermediate steps. You also can't evaluate state at intermediate steps. The only way to debug intermediate steps is to trace in the asm view. The call stack in the debugger will also not be clear, if the same methods happens multiple times in the fluent chain.

Runtime issues:

When using interfaces for the chain (rather than objects), you have to pay for the reference counting overhead, as well as a more complex exception frame. You can't have try..finally constructs in a chain, so no guarantee of closing what was opened in a fluent chain f.i.

Debug/Error logging issues:

Exceptions and their stack trace will see the chain as a single instruction, so if you crashed in .DoSomething, and the call chain has several .DoSomething calls, you won't know which caused the issue.

Code Formatting issues:

AFAICT none of the existing code formatters will properly layout a fluent call chain, so it's only manual formatting that can keep a fluent call chain readable. If an automated formatter is run, it'll typically turn a chain into a readability mess.

Everybody is just writing about negative issues so let's stress some positive issues. Errr, the only positive issue - less (in some cases much less) typing.

I wrote GpFluentXMLBuilder just because I hate typing tons of code while creating XML documents. Nothing more and nothing less.

The good point with fluent interfaces is that you don't have to use them in the fluent manner if you hate the idiom. They are completely usable in a traditional way.

EDIT: A point for the "shortness and readability" view.

I was debugging some old code and stumbled across this:

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

I knew immediately what the code does. If, however, the code would look like this (and I'm not claiming that this even compiles, I just threw it together for demo):

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);

I would need quite some time (and a cup of coffee) to understand what it does.

EDIT2:

Eric Grange made a perfectly valid point in comments. In reality, one would use some XML wrapper and not DOM directly. For example, using OmniXMLUtils from the OmniXML package, the code would look like that:

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));

Still, I prefer the fluent version. [And I never ever use code formatters.]

Are there any compiler issues?

No.

Are there any debugging issues?

Yes. Since all the chained method calls are seen as one expression, even if you write them on multiple lines as in the Wikipedia example you linked, it's a problem when debugging because you can't single-step through them.

Are there any runtime/error handling issues?

Edited: Here's a test console application I wrote to test the actual Runtime overhead of using Fluent Interfaces. I assigned 6 properties for each iteration (actually the same 2 values 3 times each). The conclusions are:

  • With interfaces: 70% increase in runtime, depends on the number of properties set. Setting only two properties the overhead was smaller.
  • With objects: Using fluent interfaces was faster
  • Didn't test records. It can't work well with records!

I personally don't mind those "fluent interfaces". Never heard of the name before, but I've been using them, especially in code that populates a list from code. (Somewhat like the XML example you posted). I don't think they're difficult to read, especially if one's familiar with this kind of coding AND the method names are meaningful. As for the one long line of code, look at the Wikipedia example: You don't need to put it all on one line of code.

I clearly remember using them with Turbo Pascal to initialize Screens, which is probably why I don't mind them and also use them at times. Unfortunately Google fails me, I can't find any code sample for the old TP code.

I would question the benefit of using "fluent interfaces".

From what I see, the point is to allow you to avoid having to declare a variable. So the same benefit the dreaded With statement brings, with a different set of problems (see other answers)

Honestly I never understood the motivation to use the With statement, and I don't understand the motivation to use fluent interfaces either. I mean is it that hard to define a variable ? All this mumbo jumbo just to allow laziness.

I would argue that rather than increase readability it, which at first glance it seems to do by having to type/read less, it actually obfuscates it.

So again, I ask why would you want to use fluent interfaces in the first place ?

It was coined by Martin Fowler so it must be cool ? Nah I ain't buying it.

This is a kind of write-once-read-never notation that is not easy to understand without going through documentation for all involved methods. Also such notation is not compatible with Delphi and C# properties - if you need to set properties, you need to rollback to using common notations as you can't chain property assignments.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!