Why should I use fluent interface in Delphi instead of using 'with command'?
I heard about both, but I haven't found the difference. I'm trying to find the best one for my project. It seems to work in the same way, just with some grammar differences.
There are some (very, very few) use cases where
withis "safe" to use but should otherwise be avoided.So I am specifically not going to get into (in detail) why you should or should not use
withand stick to answering the second part of your question: the apparent misunderstanding thatwithand fluent APIs are somehow directly related when they really are not.withwithtakes one or more symbols and creates a scope in which those symbols takes precedence over other symbols with the same name in the containing scope:So if we imagine we are writing code on some form and need to set multiple properties of some component on that form where some of those properties are also present (i.e. with the same name) on the form class itself, we can use
withto create a scope in which an unqualified reference to those properties resolves to the component, not the form:The alternative is to not use
withand instead use qualified references to the properties offoo:This is clearly not a "fluent" API. So what is?
Fluent API
A fluent API is a term used to describe a pattern where methods of an object return a reference to that object, allowing further methods to be called by chaining them.
Sticking with the hypothetical
foocomponent, if we imagine that the developer of that component had provided a fluent api for setting properties instead of (or perhaps as well as) directly exposing them as properties, then we could imagine it might be possible to write code similar to this:SetCaption()andSetTag()return the object on which they are called, allowing further calls to that object to be chained together.This superficially looks similar, although less "wordy", to the
withpattern, with two key differences:the fluent api call is a single statement; the
withapproach involves multiple statementsthe fluent api calls are made to an explicitly identified object; the calls made using
withimplicitly identify the target objectThe confusion of
withand fluent APIs perhaps stems from the fact that the use ofwithin conjunction with a fluent api is essentially just a variation in grammar:But the key differences remain. The above code is two statements implicitly using
foovs the single statement explicitly callingfooin the fluent variant.A significant additional difference is that
withcan be used with multiple symbols (if you are absolutely determined to make your life and that of your colleagues difficult :)). This has (hopefully) obvious potential to make it difficult to tell (at a glance) which references in awithscope resolve to which symbols.A fluent api, on the other hand, always starts with some object, and the chain always continues with that object. There is never any question or doubt about what symbol you are dealing with.
Just for fun, we can show that
withand fluency are orthogonal by looking at how the two could be used together to create a real monstrosity:So what is the similarity?
The only real similarity is that a fluent api provides much the same benefit of not having to qualify every call (or, more accurately, by making the qualification of each subsequent call part of the previous call).
One of the big problems with
withis that the debugger and IDE tooling to this day remains unable to resolve the symbols in the same way that the compiler does, which can lead to significant issues when trying to debug code in awithstatement. Inspecting awithscoped value in the debugger can resolve to the wrong symbol, giving the wrong value.Fluent APIs have their own debugging challenges, the most obvious being that a chained sequence of fluent api calls is a single statement on which only a single breakpoint may be placed.
Which should you use?
As the consumer of an api...
If you are faced with a scenario where you don't have a fluent api to work with, there is no choice between
withand a fluent api. Only a decision whether to usewithor not. Which is a long-running and heated debate.The short answer to this is (IMHO): don't use
withunless/until you have confidently identified the (very, very few) edge cases where it is safe and beneficial to do so. In the meantime, default to "don't".If there is a fluent api, then use it. It provides very comparable syntactic shorthand without the pitfalls of
with, though at the expense of some lack of granularity when it comes to breakpoints, as mentioned.The third alternative, of course, is to use neither
withnor fluency and simply take a reference to the "root" of the fluent api chain and use that in separate statements rather than chaining calls.As the developer of an api
Think about how your code will be used and consider whether a fluent api makes sense. "Builder" type APIs very commonly use fluent APIs, and there are other use cases, but some fluent APIs are very unintuitive and can be cumbersome to use; they are not a panacea.
And if you run into an edge case in your api where fluency isn't possible, you risk leaving your consumers in the uncomfortable position of using fluency in some areas but not being able to in others, which can be very frustrating having to remember which parts of your api work in which ways.
I hope that helps.