I know there are many variations and related topics to this one here on stack overflow but I haven't found any compelling answers so I'll give it a go myself.
I'm trying to design a builder factory that returns different subclasses of a common builder interface. I want to allow all the implementations to share a common abstract class for code re-use.
Note that I'm not interested in the return type of the build()
method, only what types the builders are.
This is what I have so far:
Builder interface with generic for the sub-interfaces:
interface FruitBuilder<T extends FruitBuilder<T>> {
T taste(String taste);
T shape(String shape);
T weight(String weight);
Fruit build();
}
Some builders have additional methods:
interface GrapesBuilder extends FruitBuilder<GrapeBuilder> {
GrapesBuilder clusterSize(int clusterSize);
}
Next is to specify a factory that returns the specific builders:
interface FruitBuilderFactory {
GrapesBuilder grapes();
AppleBuilder apple();
LemonBuilder lemon();
}
A user of these interfaces should be able to use it like:
Fruit grapes = fruitBuilderFactory
.grapes()
.weight(4)
.color("Purple")
.clusterSize(4) // Note that the GrapesBuilder type must be accessible here!
.build();
Most of the logic would go into the abstract class, including advanced build logic:
abstract class BaseFruitBuilder<T extends FruitBuilder<T>> implements FruitBuilder<T> {
String taste;
T taste(String taste) {
this.taste = taste;
return (T)this; // Ugly cast!!!!!
}
...
Fruit build() {
Fruit fruit = createSpecificInstance();
// Do a lot of stuff on the fruit instance.
return fruit;
}
protected abstract Fruit createSpecificInstance();
}
Given the base class, it's really simple to implement new builders:
class GrapseBuilderImpl extends BaseFruitBuilder<GrapesBuilder> {
int clusterSize;
GrapesBuilder clusterSize(int clusterSize) {
this.clusterSize = clusterSize;
}
protected Fruit createSpecificInstance() {
return new Grape(clusterSize);
}
}
This is all compiling and fine (at least my real code). The question if whether or not I can remove the ugly cast to T in the abstract class.
One option to avoid casting is to define a single abstract method returning T
:
abstract class BaseFruitBuilder<T extends FruitBuilder<T>> implements FruitBuilder<T> {
String taste;
T taste(String taste) {
this.taste = taste;
return returnThis();
}
protected abstract T returnThis();
//...
}
class GrapseBuilderImpl extends BaseFruitBuilder<GrapesBuilder> {
//...
@Override
protected T returnThis() {
return this;
}
}
The downside is that you have to trust each subclass to implement the method correctly. Then again, with your approach, there's nothing stopping anyone from declaring a subclass GrapesBuilder extends BaseFruitBuilder<AppleBuilder>
, so you'll need to trust subclasses to some extent.
EDIT Just realized this solution was referenced by @user158037's comment. I've used this myself, but never realized it was a known idiom. :-)