Fluent Interfaces - Let the API tell the story
In my last post about Creating a decent API for client side script registration, Eran raised a few great comments about the readability and proper usage of this style of coding. I decided to answer his questions with a post, as my comment started to fill enough paper to clean a Brazilian forest or two (well, in terms of a response).
Introduced by Martin Folwer, Fluent Interfaces ables the programmer to supply an API that can be used to build a genuine use-case in the system or just a complete logical query\request from a service. This coding style is quite different from the traditional 101 lessons in OOP school. The biggest benefit of Fluent Interface, in my opinion, is that you can read the code out load like the customer is talking to you instead of the programmer that wrote it. Sometimes it gets even better, you can read someone’s else code like she\he was next to you, explaining what she\he meant do do. My take is that using a method to describe use-case\action\query\request will be (almost)always better, in terms of readability, than using parameter(s) as you’ll need the IntelliSense to understand the latter. Here is a simple API, the first one is traditional OOP while the second one applies Fluent Interfaces. Please bare in mind that these samples were written just to set the ground for the difference between these two coding technique:
// take 1 – traditional style
public class ClientSideExtender
{
public void CallMethod(string methodName, RunAt runScriptAt, bool ignoreExceptions, params object[] parameters);
}
// take 2 – Fluent Interfaces
public class ClientSideExtender
{
public ScriptCommand CallMethod(string methodName);
}
public class ScriptCommand
{
public ScriptCommand WithParameters(params object[] parameters);
public ScriptCommand When(RunAt runScriptAt);
public ScriptCommand IgnoreExceptions();
}
Assuming that we have a javascript method with this signature “markRow(rowId, shouldDisableOtherRows)”, here is how can one use these API to register client-side method call(accordingly):
clientSideExtender.CallMethod(“markRow”, RunAt.AfterPageLoaded, true, “5”, true);
clientSideExtender.CallMethod(“markRow”).WithParameters(“5”, true).When(RunAt.AfterPageLoaded).IgnoreExceptions();
Obviously, both API will create the same code eventually: <script …>markRow(“5”, true);</script>.
What I really love about Fluent Interfaces is that I don’t need the freakin’ IntelliSense in order to understand what “true” means as a parameter(the difference is marked in red). It ables me to read it out load – I want to call a client-side method named “markRow”, with 2 parameters, execute it after the page is loaded and wrap the entire thing to swallow exceptions (assume that someone else will take care of it). If you want to call a method that doesn’t get any parameter, don’t call to WithParameters method. You can always change the order of the calls if you see it fit (maybe calling IgnoreException before When).
One of the blames I hear(again and again) about Fluent Interfaces is that it “allows” programmers to abuse the code. “You can change the order of the calls or forget to call one and make a big mess” is a common response to the concept. To be totally honest, I don’t eat it as programmers can make a mess of pretty much anything. We’ve all been there, right? I agree that it requires some different way of thinking about creating & using API, but then again, so does learning a new programming principle, a design pattern or a coding techinque. It took several years until people started to chew TDD and accept the advantages of using it. My guess is that in ~1-2 years, Fluent Interfaces will be much more common in the way we’re writing and using code (LINQ rings a bell? well OK, leaving the “sql-like” synthetic sugar aside).
This leads me to my believe about designing Fluent Interface. I say – when appropriate, why not allowing the programmer to choose?
I would create two overloads for CallMethod, as shown above, and let the programmer decide which one she\he would like to use.
I would use Fluent Interface.