Meeting 6
When: Monday, November 3rd, 2008 - 19:00
Where: Freihaus HS4
Bazaar
Held by Florian M.
Download the slides (pdf, 68KB).
Fluent Interfaces
Unfortunately there are no slides available for the presentation from Axel G. But you can have a look at Martin Fowler's article about fluent interfaces who kind of coined the term.
To be short, it's kind of an internal DSL with the aim to provide a readable API.
The following example shows the use of JMock:
<source lang="java">
mock.expects(once()).method("m").with(or(stringContains("hello"),
stringContains("howdy")));
</source>
In a preceding presentation about db4o we had similar code for querying a OO database:
<source lang="java">
query.descend("matrNr").constrain(500000).greater().equal();
</source>
Simple Example
Personally, I (Christoph Pickl) like to use that technique to construct complex objects which should be on the one side immutable but on the other easy to create (values for creation are sometimes scattered throughout many different classes). Imagine you have a Person
type with its only attributes name:String
and age:int
which are both marked final.
<source lang="java"> public class Person {
// final attributes private final String name; private final int age;
// constructor is package-private, so only PersonBuilder can access it Person(final String name, final int age) { this.name = name; this.age = age; }
// simple getter methods public String getName() { return this.name; } public int getAge() { return this.age; }
} </source>
A PersonBuilder
now provides the single possibility to create Person
s. The interesting thing here is, that it forces the caller to invoke methods for each attribute of Person
, and returns itself but with another "face" (builder interface). Only if the last attribute was set the create()
method is accessible which finally returns a new instance.
Guard clauses (actually used to avoid nested conditions) help to ensure that the instance will only store valid values. This gives the advantage of defining a valid state outside of the entity itself so it can be reused in a complete different environment with complete different limitations (name can be null, etc).
<source lang="java">
public class PersonBuilder implements IPersonBuilder1, IPersonBuilder2, IPersonBuilderFinisher {
// (non-final) attributes private String name; private int age;
// constructor private PersonBuilder() { // constructor not visible; use static factory method build() } public static IPersonBuilder1 build() { return new PersonBuilder(); }
// fluent interface implementations public IPersonBuilder2 name(final String name) { if(name == null) { // guard clause throw new IllegalArgumentException("name == null"); } this.name = name; return this; }
public IPersonBuilderFinisher age(final int age) { if(age < 0 || age > 200) { // guard clause throw new IllegalArgumentException("age not within range 0-200: " + age); } this.age = age; return this; }
public Person newInstance() { return new Person(this.name, this.age); }
}
</source>
Two obvious disadvantages come to my mind: The overhead of creating an interface for each method plus one for the creation and you have to invoke the methods in exactly the way they are defined via the fluent interfaces. You can't change the order, and you can't skip any attribute.
<source lang="java">
public interface IPersonBuilder1 {
IPersonBuilder2 name(String name);
} public interface IPersonBuilder2 {
IPersonBuilderFinisher age(int age );
} public interface IPersonBuilderFinisher {
Person newInstance();
} </source>
Finally you can use the builder to create Person
instances via a very readable API. The second Person
instance is created in a distributed manner, which would not be that easy if you would have to create the instance in the old-fashioned way because of final attributes you have to pass to the constructor.
<source lang="java">
public class App {
public static void main(final String[] args) { // short version: final Person p1 = PersonBuilder.build().name("Christoph Pickl").age(23).newInstance();
// or to use different builders and pass them around final IPersonBuilder2 builder2 = PersonBuilder.build().name("Christopherus Pickl"); final Person p2 = App.computeAge(builder2).newInstance(); }
private static IPersonBuilderFinisher computeAge(final IPersonBuilder2 builder2) { final int age = 42; // some complicated computation return builder2.age(age); }
} </source>