Fluent interfaces: Introduction

Fluent interfaces are becoming more and more popular in C#. This is mostly due to Lambda expressions, Expression trees (, the conversion between each other) and Extension methods.

Basically, fluent interfaces (first coined by Martin Fowler and Eric Evans) are ways to develop APIs in a more readable fashion. They also tend to be very easily discoverable (letting IntelliSense lead the user).

Let's have a look at two calls. The first one is from Fluent Nhibernate and the second one is from NUnit:

Map(x => x.Username)

Assert.That("gâteau au fromage", Is.Not.EqualTo("cheese cake"));

The previous call is really eloquent; Try saying it out loud: "Assert that 'gâteau au fromage' is not equal to 'cheese cake'" (Yes, gâteau au fromage is french for cheese cake...) It does what it says it does; simple.

NUnit's example is a little less "fluent" in my opinion. The following syntax seems more fluent to me:

// Not an actual NUnit call...
Assert.That("gâteau au fromage")
      .Is.Not.EqualTo("cheese cake")

The last example puts emphasis on something very important for fluent interfaces: Method chaining. Chaining methods allows the API (along with IntelliSense) to guide the user through available methods. Some non-fluent APIs can be harder to "discover". Let's compare the previous fluent assertion the its non-fluent equivalent:

string cake = "gâteau au fromage";
Assert.AreEqual(cake, "cheese cake");

It doesn't seem as readable as its fluent counterpart. Put some lambda expressions into the mix and all hell breaks loose!

Assert.ReturnEqual(x => x * x, x => x + x, 2);

Assert.That(x => x * x)
    .Returns(x => x + x)

To be honest, the first line almost looks like random symbols and punctuations. The fluent statement is straight forward. (Bonus: Can this statement succeed for any number other than 2 and 0?)

In non-fluent APIs, different contexts might require different classes (like CollectionAssert for collections) and also require the user to know about them beforehand. How can I know about all of the assertion utility classes? Is there a ComparableAssert class? Is there a CheeseCakeAssert class as well? The first-time user doesn't know. It's not as discoverable.

Every rose has its thorn and fluent interfaces do have down sides.

Most notably, they tend to be somewhat obscure. Chaining multiple calls prevents from letting intermediate values out gracefully. While it is still possible for the user to break the chain, it pretty much ruins the readability; making it worst than its non-fluent equivalent.

Fluent interfaces are also hard to debug. Since the whole method chain is a single statement (!), Visual Studio's debugger steps over it in a blink of an eye; Even quick watches are almost useless since its common for a fluent interface to change its state after each method call.

Wa..wait. Change its state? That is one thing we haven't mentioned yet: Fluent interfaces are also a great way to abstract state machines.

Bonus answer: The mathematical approach leads us toward infinity (double.PositiveInfinity). Mathematically, there is no other integer that can fulfill both conditions. However, one might want to take advantage of integer overflow and come up with the following Int32 values: -2147483648 (int.MinValue) and -2147483646 (int.MinValue + 2). Mathematical proof is left to the reader (I've always wanted to say that.)

Posted by: Bryan Menard
Last revised: 22 Oct, 2011 06:37 PM History


18 Oct, 2009 01:05 PM

Thank you for teaching me this practice, I like it a lot and have started using it in a PHP project I'm doing with a friend of mine. We're using it for both a query builder and a form validator.

For, example, to generate a prepared query, I use:

$queryBuilder->select->fields('id', 'name', 'email')->from('users')->prepare();

And to validate a password field, I use :

$form->validateThat('password')->is()->not()->empty("You have not entered a password.")
     ->and()->longerThanOrEqualTo(8, "Your password must be at least 8 characters long.")
     ->and()->shorterThanOrEqualTo(50, "Your password is too long.")
     ->and()->identicalTo('passwordConfirm', "The passwords you have entered don't match.");

No new comments are allowed on this post.