Saturday, March 15, 2014

Fluent Interfaces (With Ruby Examples)

As of late, a hammer of mine has been the use of fluent interfaces. Fluent interfaces are simply classes that, instead of having methods that return something uninteresting or nothing, return the calling instance.

So, in a general, strongly-typed language this might look like:
public class Foo {
    public Foo FluentMethod() {
        // do some stuff
        return this;
    }
}

For simplicity's sake, I will use Ruby for the rest of this post. In the Ruby language this would look more like:
class Foo
  def fluent_method()
    # do some stuff
    self
  end
end

One of the biggest benefits of fluent interfaces is that they make change easier. For example consider the following, non-fluent code:
a_chicken = Chicken.new

mr_fox = Fox.new
mr_fox.is_quite(:fantastic)
mr_fox.lives_in(:a_tree)
mr_fox.eat(a_chicken)

This code annoys me (somewhat) because mr_fox appears on four lines, which implies that if you wanted to change it, you would have to change each of the four lines. Also, if I fat-fingered one of those references, I would waste time fixing it when the interpreter caught it. Here comes fluent to the rescue:
a_chicken = Chicken.new

Fox.new
   .is_quite(:fantastic)
   .lives_in(:a_tree)
   .eat(a_chicken)

Isn't that better? mr_fox doesn't even exist anymore. Also, in my humble opinion, this style of method chaining greatly improves readability.

There's also a hidden benefit in this example: fluent interfaces logically group operations of a single object onto, technically, one SLOC. For example, if I didn't use fluent method chaining, there's nothing stopping me from mixing Chicken logic with Fox logic:
a_chicken = Chicken.new
mr_fox = Fox.new
mr_fox.is_quite(:fantastic)
a_chicken.is_named("rodrick")
mr_fox.lives_in(:a_tree)
a_chicken.still_has_its_head(true)
mr_fox.eat(a_chicken)

But if I made Fox fluent and described it with method chaining then there's no way for me to interleave Chicken logic, and, frankly, create a mess:
a_chicken = Chicken.new
a_chicken.is_named("rodrick")
a_chicken.still_has_its_head(:eggs)

Fox.new
   .is_quite(:fantastic)
   .lives_in(:a_tree)
   .eat(a_chicken)

Of course, I (at this point in my software career) would probably make Chicken fluent as well, but I think it's fairly clear what that would look like at this point.

Hopefully you've come to dig fluent interfaces (at least a little) at this point. To me it's pretty cool that (in dynamic languages) implementing a fluent interface can be as simple as tacking a four-letter-word (self) on the end of methods whose return value we found otherwise uninteresting.

No comments:

Post a Comment