I have an object hierarchy that increases in complexity as the inheritance tree deepens. None of these are abstract, hence, all of their instances serve a, more or less sophisticated, purpose.
As the number of parameters is quite high, I would want to use the Builder Pattern to set properties rather than code several constructors. As I need to cater to all permutations, leaf classes in my inheritance tree would have telescoping constructors.
I have browsed for an answer here when I hit some problems during my design. First of, let me give you a simple, shallow example to illustrate the problem.
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder { protected String sex; protected String name; public Builder() { } public Builder sex(String sex) { this.sex = sex; return this; } public Builder name(String name) { this.name = name; return this; } public Rabbit build() { return new Rabbit(this); } } } public class Lop extends Rabbit { public float earLength; public String furColour; public Lop(LopBuilder builder) { super(builder); this.earLength = builder.earLength; this.furColour = builder.furColour; } public static class LopBuilder extends Rabbit.Builder { protected float earLength; protected String furColour; public LopBuilder() { } public Builder earLength(float length) { this.earLength = length; return this; } public Builder furColour(String colour) { this.furColour = colour; return this; } public Lop build() { return new Lop(this); } } }
Now that we have some code to go on, imaging I want to build a Lop
:
Lop lop = new Lop.LopBuilder().furColour("Gray").name("Rabbit").earLength(4.6f);
This call will not compile as the last chained call cannot be resolved, Builder
not defining the method earLength
. So this way requires that all calls be chained in a specific order which is very impractical, especially with a deep hierarchy tree.
Now, during my search for an answer, I came across Subclassing a Java Builder class which suggests using the Curiously Recursive Generic Pattern. However, as my hierarchy does not contain an abstract class, this solution will not work for me. But the approach relies on abstraction and polymorphism to function which is why I don't believe I can adapt it to my needs.
An approach I have currently settled with is to override all methods of the superclass Builder
in the hierarchy and simply do the following:
public ConcreteBuilder someOverridenMethod(Object someParameter) { super(someParameter); return this; }
With this approach I can assure I am being returned an instance I can issue chain calls on. While this is not as worse as the Telescoping Anti-pattern, it is a close second and I consider it a bit "hacky".
Is there another solution to my problem that I am not aware of? Preferably a solution consistent with the design pattern. Thank you!
This is certainly possible with the recursive bound, but the subtype builders need to also be generic, and you need a few interim abstract classes. It's a little bit cumbersome, but it's still easier than the non-generic version.
/** * Extend this for Mammal subtype builders. */ abstract class GenericMammalBuilder<B extends GenericMammalBuilder<B>> { String sex; String name; B sex(String sex) { this.sex = sex; return self(); } B name(String name) { this.name = name; return self(); } abstract Mammal build(); @SuppressWarnings("unchecked") final B self() { return (B) this; } } /** * Use this to actually build new Mammal instances. */ final class MammalBuilder extends GenericMammalBuilder<MammalBuilder> { @Override Mammal build() { return new Mammal(this); } } /** * Extend this for Rabbit subtype builders, e.g. LopBuilder. */ abstract class GenericRabbitBuilder<B extends GenericRabbitBuilder<B>> extends GenericMammalBuilder<B> { Color furColor; B furColor(Color furColor) { this.furColor = furColor; return self(); } @Override abstract Rabbit build(); } /** * Use this to actually build new Rabbit instances. */ final class RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> { @Override Rabbit build() { return new Rabbit(this); } }
There's a way to avoid having the "concrete" leaf classes, where if we had this:
class MammalBuilder<B extends MammalBuilder<B>> { ... } class RabbitBuilder<B extends RabbitBuilder<B>> extends MammalBuilder<B> { ... }
Then you need to create new instances with a diamond, and use wildcards in the reference type:
static RabbitBuilder<?> builder() { return new RabbitBuilder<>(); }
That works because the bound on the type variable ensures that all the methods of e.g. RabbitBuilder
have a return type with RabbitBuilder
, even when the type argument is just a wildcard.
I'm not much of a fan of that, though, because you need to use wildcards everywhere, and you can only create a new instance using the diamond or a raw type. I suppose you end up with a little awkwardness either way.
And by the way, about this:
@SuppressWarnings("unchecked") final B self() { return (B) this; }
There's a way to avoid that unchecked cast, which is to make the method abstract:
abstract B self();
And then override it in the leaf subclass:
@Override RabbitBuilder self() { return this; }
The issue with doing it that way is that although it's more type-safe, the subclass can return something other than this
. Basically, either way, the subclass has the opportunity to do something wrong, so I don't really see much of a reason to prefer one of those approaches over the other.
If anyone still bumped into the same problem, I suggest the following solution, that conforms "Prefer composition over inheritance" design pattern.
Parent class
The main element of it is the interface that parent class Builder must implement:
public interface RabbitBuilder<T> { public T sex(String sex); public T name(String name); }
Here is the changed parent class with the change:
public class Rabbit { public String sex; public String name; public Rabbit(Builder builder) { sex = builder.sex; name = builder.name; } public static class Builder implements RabbitBuilder<Builder> { protected String sex; protected String name; public Builder() {} public Rabbit build() { return new Rabbit(this); } @Override public Builder sex(String sex) { this.sex = sex; return this; } @Override public Builder name(String name) { this.name = name; return this; } } }
The child class
The child class Builder
must implement the same interface (with different generic type):
public static class LopBuilder implements RabbitBuilder<LopBuilder>
Inside the child class Builder
the field referencing parentBuilder
:
private Rabbit.Builder baseBuilder;
this ensures that parent Builder
methods are called in the child, however, their implementation is different:
@Override public LopBuilder sex(String sex) { baseBuilder.sex(sex); return this; } @Override public LopBuilder name(String name) { baseBuilder.name(name); return this; } public Rabbit build() { return new Lop(this); }
The constructor of Builder:
public LopBuilder() { baseBuilder = new Rabbit.Builder(); }
The constructor of builded child class:
public Lop(LopBuilder builder) { super(builder.baseBuilder); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With