Wednesday, June 09, 2010

Using inheritance with fluent interfaces: get this

Recently I had a situation where I needed to implement Joshua Bloch's Builder pattern (see Effective Java, item 2) over a hierarchy of Java domain objects. Similar problems would arise when building other types of fluent interface, which commonly "return this" from each method in order to support method chaining. Creating a working solution presented some interesting challenges!

Without going into too many details, the fluent Builder pattern is needed in languages without named arguments and default arguments, like Java, in order to avoid long lists of constructor parameters, or a bunch of setters on the object (which may be immutable). For example:

public class X {
protected int foo;
protected int bar;

public static class Builder {
private X x = new X();

public Builder withFoo( int foo ) {
x.foo = foo; return this;
}
public Builder withBar( int bar ) {
x.bar = bar; return this;
}
public X build() { return x; }
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

You would then use this code as follows:

X x = new X.Builder().withFoo( 1 ).withBar( 2 ).build();

Obviously, for a class with only two instance variables, this pattern doesn't make much sense -- you'd just use a constructor. But for domain objects with lots more than two instance variables, or with subclasses that may add more than two, it makes plenty of sense.

However, this becomes more challenging when dealing with objects that use inheritance. In the project I'm working on, we had a number of domain objects that were (legitimately) using inheritance. The trouble is that the builder pattern doesn't particularly handle this well as it stands.

The simplest way of creating builders for inherited classes is simply to duplicate the builder methods:

public class Y extends X {
private int baz;

public static class Builder {
private Y y = new Y();

public Builder withFoo( int foo ) {
y.foo = foo; return this;
}
public Builder withBar( int bar ) {
y.bar = bar; return this;
}
public Builder withBaz( int baz ) {
y.baz = baz; return this;
}
public Y build() { return y; }
}

protected Y() {}

public int getBaz() { return baz; }
}

But duplication is EvilTM, especially if you have lots of builder methods, or lots of subclasses. We really don't want both Builder classes to have the withFoo and withBar methods. How do we get around this?

My first thought was something along the following lines:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X> {
private T x;

public Builder() { x = createX(); }
public Builder<T> withFoo( int foo ) {
x.foo = foo; return this;
}
public Builder<T> withBar( int bar ) {
x.bar = bar; return this;
}
public T build() { return x; }
protected abstract T createX();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder extends X.Builder<Y> {
public Builder withBaz( int baz ) {
y.baz = baz; return this;
}
protected Y createX() { return new Y(); }
}

protected Y() {}

public int getBaz() { return baz; }
}

The only trouble with that is that the following fails to compile:

Y y = new Y.Builder().withFoo( 1 ).withBaz( 3 ).build();

Why? Because withFoo returns a Builder<Y>, not a Y.Builder; and the withBaz method is on the latter, not the former.

So...the code I actually wrote looked like this:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X,
B extends Builder<T, B>> {

private T obj;

public Builder() { obj = createObj(); }
public B withFoo( int foo ) {
obj.foo = foo; return this;
}
public B withBar( int bar ) {
obj.bar = bar; return this;
}
public T build() { return built; }
protected abstract T createObj();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder
extends X.Builder<Y, Y.Builder> {

public Builder withBaz( int baz ) {
obj.baz = baz; return this;
}
protected Y createObj() { return new Y(); }
}

protected Y() {}

public int getBaz() { return baz; }
}

Now the return types are correct...but the withFoo and withBar methods won't compile. The trouble is that this inside X.Builder<T, B> is of type X.Builder<T, B>, not of type B. Actually, at runtime, the builder class should indeed be of type B, but it would be inelegant and type-unsafe to cast this to B everywhere.

Happily, there is a solution that doesn't involve casting. This is the final version of the code:

public abstract class X {
protected int foo;
protected int bar;

protected static class Builder<T extends X,
B extends Builder<T, B>> {

private T obj;
private B thisObj;

public Builder() {
obj = createObj(); thisObj = getThis();
}
public B withFoo( int foo ) {
obj.foo = foo; return thisObj;
}
public B withBar( int bar ) {
obj.bar = bar; return thisObj;
}
public T build() { return built; }
protected abstract T createObj();
protected abstract B getThis();
}

protected X() {}

public int getFoo() { return foo; }
public int getBar() { return bar; }
}

public class Y extends X {
private int baz;

public static class Builder
extends X.Builder<Y, Y.Builder> {

public Builder withBaz( int baz ) {
obj.baz = baz; return thisObj;
}
protected Y createObj() { return new Y(); }
protected Builder getThis() { return this; }
}

protected Y() {}

public int getBaz() { return baz; }
}

We added the getThis() method, which is always implemented as return this; in each subclass. This ensures that the Builder subclass is in fact the B type parameter to X.Builder<T, B>.

So, at the price of a small wart (the getThis() method plus associated instance variable), we've got a solution that maintains type safety, removes duplication, and allows all the code completion goodness that your IDE of choice may offer. Win!

2 comments:

eric said...

Great post, Eric. For a moment, I wondered why bother with the Builder in this case, when you could just make the mutator methods fluent. So you would have

X x = new X().setFoo(foo).setBar(bar);

On the other hand, this wouldn't be acceptable for purely initialization parameters that should be immutable.

Thanks for posting this.

Alexei Udalov said...

Hello it's wonderfull post.
But what a variable built
in public T build() { return built; } method?