Fluent interfaces and state machines (part 1) - State-per-operation pattern

Fluent interfaces are a relatively new concept in object-oriented programming. Although it is not object-oriented, so to speak, it represents a compromise between object-oriented and functional way of thinking.

The real challenge with fluent interfaces is to have them "speak" to the user: Formulating sentences of phrases greatly improve the usability of a fluent API. Take this, for instance:

Assert.That(x).Is.Null();

It's quite easy to guess what is this instruction doing. That is: Asserting that x is null. Notice how close the fluent instruction is to the actual "definition" of the behaviour.

Let's use the previous example to see how each call can be represented, in fluent interfaces. An API used to make assertions will most probably have a great number of potential expressions. Instead of defining each and every operation individually, we may allow the user to construct the expression using a syntax really close an english phrase. That being said, we also want the API to restrict the possibilities, therefore guiding the user into a more "discoverable" interface.

State-per-operation pattern

Designing fluent interfaces in static languages like C# is not an easy task. Be prepared to have lots of interfaces and methods.

State-per-operation (simple)

The simplest pattern that can be used to represent fluent interfaces is to use state machines, in a usage-first approach. That means designing your fluent interface first, and then make a mental map (or UML diagram like I did) of the corresponding state machine. The simplest way of doing this is to:

  • Add a state for every operation
  • Add a transition where it is legal to chain calls between two methods (states)

Let's say that we want to represent the following API:

Assert.That(x).Is.Null();

Assert.That(x).Is.Not.Null();

What you get is the diagram on the right. It's not bad: In a state machine, only certain transitions are allowed, thus allowing us to restrict the possibilities. The loopback (from Null to That) is a classic trait of fluent state machines; We want to "reset" the state machine so the user can fluently add another expression. It this case, it allows for calls like:

Assert.That(x).Is.Null()
              .Is.Not.Null();

It's all good. We can easily materialize that state machine into interfaces by defining an interface per operation, and a method/property per transition.

public static class Assert
{
    public static IFluentThat That<T>(T x) { /* ... */ }
}

public interface IFluentThat
{
    IFluentIs Is { get; }
}

public interface IFluentIs
{
    IFluentThat Null();
    IFluentNot Not { get; }
}

public interface IFluentNot
{
    IFluentThat Null();
}

Loopback states are usually not materialized into an interface. They are also preferably represented as a methods instead of properties. This is why there's no IFluentNull interface. This pattern generally works well, but does have some downsides.

While this works well for a small amount of methods (one or two), it quickly grows out of control when adding other methods. Let's say we want to add a "between x and y" functionality to our assert API:

State-per-operation (Complex)

Assert.That(x).Is.Between(y).And(z);

A glimpse of the diagram on the right will suffice to convince you that it's not a good approach when designing fluent APIs. It grows really complex, real fast. And chances are, if you are opting for a fluent API, the functionality your trying to expose is complex. Bummer.

So, this approach:

  1. Think of the usage
  2. Represent each operation as a state in a state machine (with corresponding transitions)
  3. Materialize each state into an interface (and each transition as a method/property)

does not work well for complex APIs.

More fundamentally, this pattern does not represent the state machine as it wants to be. There is no "Null" state; It's an operation, not a state. It really wants to be a transition.

In my next post, I'll try and explain another way of pairing fluent interface with state machines.

fluent-interfacec#
Posted by: Bryan Menard
Last revised: 20 Nov, 2011 04:13 AM History

Comments

Laurent
Laurent
23 Nov, 2011 02:33 PM

Where would you say the line is between a fluent interface and method chaining? A fluent interface uses method chaining, but using method chaining doesn't necessarily imply a fluent interface (e.g. C# StringBuilder or jQuery).

When would you use chaining without being perfectly fluent? I like fluent interfaces, but fear they might too verbose at times and that you need to chain too much for a simple operation. In your post, for example, what is the advantage of Assert.That(x).Is.Not.Null(); over something like Assert.NotNull(x); ? I get that it's less "state-machine-like", but it is [almost] as readable.

In my opinion, fluent interfaces are more interesting when used for complex operations, such as building a query. There is a tendency to make fluent interfaces for everything, and sometimes I find it quite useless. I saw this on a blog about fluent interfaces:

var person = new PersonFactory()
                .Initialize()
                .AddFirstName("Raffaele")
                .AddMiddleName(string.Empty)
                .AddLastName("Garofalo")
                .Create();

Honestly, I'm not a fan. I think it is just unnecessary, as this does the same thing, without the use of a factory created just for the kicks of it:

var person = new Person { FirstName = "Raffaele", MiddleName = string.Empty, LastName = "Garofalo" };

What's your opinion on this subject ?

Bryan Menard
Bryan Menard
23 Nov, 2011 04:06 PM

Method chaining is simply a trait of fluent interfaces, you are right. What defines fluent interfaces is the "fluent feel" it gives to your code. Take the following, for instance:

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

Assert.That(x => x * x)
      .Returns(x => x + x)
      .For(2);

The fluent version of this assertion is actually readable and easily understandable for anyone who comes across this code bit.

One other peculiarity of fluent interfaces is to be able to restrict the user from doing certain operations, at certain points. Take LINQ to objects, for example:

// Legal
Enumerable.Empty<Person>()
          .OrderBy(x => x.FirstName)
          .ThenBy(x => x.LastName);

// Not legal
Enumerable.Empty<Person>()
          .ThenBy(x => x.LastName)
          .OrderBy(x => x.FirstName);

It makes sense that you can't ThenBy before you OrderBy (I don't even know what it would mean...) And this is a great example of a fluent interface:

  • It reads fluently (Order by first name, then by last name)
  • It dictates when to use each method (OrderBy before ThenBy)
  • It keeps an internal state to build are comparator

I don't think the example you cited with the PersonFactory is a fluent interface. It's a common use of the Builder Pattern, using method chaining "setters" instead of unchainable properties. It's just that: method chaining.

My take of fluent interfaces? Yes, they tend to be overused and misused sometimes, you are entirely right. And my example with the Assert.That(x).Is.Not.Null() is probably overkill. But I believe it's when the API is very complex that fluent interfaces can help the most, by guiding users through an, otherwise, complex regiment of rules and set of classes.

No new comments are allowed on this post.