In this post I will show how something as non-intuitive (read ugly) as recursive generics can help to solve problem of creating extendable Builders.
But first we need to define what are we trying to achieve. We need to build set of Builder objects that facilitate creation of non-trivial domain objects and provide fluent interface (for the definition of fluent interface see Martin Fowler’s article) for user along the lines.
So let’s start with defining our 3 builder classes:
- BaseBuilder
- ConstraintBuilder
- And finally ConcreteBuilder
Now we want to write client code that uses ConcreteBuilder
. The code we want to have is the following:
Notice however that this code has compilation error:
"The method equalTo(int) is undefined for the type BaseBuilder"
.
Java compiler tells us that we are trying to invoke method on the class BaseBuilder
whereas we are assuming to be working with class ConcreteBuilder
.
OK, so how do we suppose to fix the code. I know of 2 possible solutions:
- Use covariant return types and override methods from base classes
- Use recursive generics when defining builder classes
Covariant return types
In object-oriented programming, a covariant return type of a method is one that can be replaced by a “narrower” type when the method is overridden in a subclass.
Thus knowing this we can fix our builders by overriding methods from parent classes and forcing them to return instance of the leaf class instead. So here is how our classes end up looking in the end:
- BaseBuilder (not changed)
- ConstraintBuilder
- ConcreteBuilder
With these changes in place our client code now compiles and runs, but we had to do too much work in my opinion. Notice for instance that we had to override all methods from base classes in each and every derived class.
We are forced to do this whenever subclass adds new methods, cause we need to provide end user with flexibility of invoking methods in any order irrespective of where they are defined.
Also in this example it was not necessary to do this for ConcreteBuilder
class but I showed it here for completeness, mainly to stress how much involved first approach is.
Enough for the ugliness, so let’s checkout second approach.
Recursive generics
OK, so is there better way to achieve the same goal without repeating same steps for each builder subclass? The answer is yes.
We can use generics while defining our builders to tell Java that return type of methods is not the builder’s class but rather the subclass of the builder, hence recursive generic definition.
Let’s see what it means for our builders:
- BaseBuilder
- ConstraintBuilder
- ConcreteBuilder
And the client code haven’t changed at all, it still compiles and runs as before:
Let’s have a quick recap of what we just did. We changed definition of the BaseBuilder
class to contain recursive generic parameter E (i.e. BaseBuilder<E extends BaseBuilder>
). This allowed us change return type of the methods from BaseBuilder
to E
, which effectively tells Java compiler that method returns some subclass of BaseBuilder
class.
Then we did the same for ConstraintBuilder
class (i.e. ConstraintBuilder<E extends ConstraintBuilder>
) which again tells javac that return type is some subclass of ConstraintBuilder
class.
And finally we stopped recursion for class ConcreteBuilder
by specifying itself as the generic parameter while extending ConstraintBuilder
class (i.e. ConcreteBuilder extends ConstraintBuilder<ConcreteBuilder>
). Since this class is at the bottom of our inheritance hierarchy we could stop using generic parameter. However if it would have subclasses on it’s own then we would have to repeat declaration similar to that of ConstraintBuilder
class.
Notice how much less effort was to implement second solution. Despite of some minor noise such as usage of @SuppressWarnings("unchecked")
annotation to eliminate compilation warnings and casts to E
this solution is much cleaner, easier to understand and maintain. There is no code duplication that we saw in first solution and it scales well when the inheritance hierarchy is getting bigger and deeper.
To conclude:
Java’s generics can be real pain (especially when wildcards are used), but in some cases clever usage of generics can save your day! 😉