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 soph
The following IEEE conference contribution Refined Fluent Builder in Java gives a comprehensive solution to the problem.
It dissects the original question into two sub-problems of inheritance deficiency and quasi invariance and shows how a solution to these two sub-problems opens for inheritance support with code reuse in the classical builder pattern in Java.
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.
As you cannot use generics, now probably the main task is to somehow loosen typing. I don't know how you process those properties afterwards, but what if you used a HashMap for storing them as key-value pairs? So there will be just one set(key, value) wrapper method in the builder (or builder might not be necessary any more).
The downside would be additional type castings while processing the stored data.
If this case is too loose, then you could keep the existing properties, but have a general set method, which uses reflection and searches for setter method on the basis of 'key' name. Although I think reflection would be an overkill.
The most easy fix would be to simply override the setter methods of the parent class.
You avoid generics, it's easy to use, extend and to understand and you also avoid code duplication when calling super.setter.
public class Lop extends Rabbit {
public final float earLength;
public final String furColour;
public Lop(final 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() {}
@Override
public LopBuilder sex(final String sex) {
super.sex(sex);
return this;
}
@Override
public LopBuilder name(final String name) {
super.name(name);
return this;
}
public LopBuilder earLength(final float length) {
this.earLength = length;
return this;
}
public LopBuilder furColour(final String colour) {
this.furColour = colour;
return this;
}
@Override
public Lop build() {
return new Lop(this);
}
}
}
Confronted with the same issue, I used the solution proposed by emcmanus at: https://community.oracle.com/blogs/emcmanus/2010/10/24/using-builder-pattern-subclasses
I'm just recopying his/her preferred solution here. Let say we have two classes, Shape
and Rectangle
. Rectangle
inherits from Shape
.
public class Shape {
private final double opacity;
public double getOpacity() {
return opacity;
}
protected static abstract class Init<T extends Init<T>> {
private double opacity;
protected abstract T self();
public T opacity(double opacity) {
this.opacity = opacity;
return self();
}
public Shape build() {
return new Shape(this);
}
}
public static class Builder extends Init<Builder> {
@Override
protected Builder self() {
return this;
}
}
protected Shape(Init<?> init) {
this.opacity = init.opacity;
}
}
There is the Init
inner class, which is abstract, and the Builder
inner class, that is an actual implementation. Will be useful when implementing the Rectangle
:
public class Rectangle extends Shape {
private final double height;
public double getHeight() {
return height;
}
protected static abstract class Init<T extends Init<T>> extends Shape.Init<T> {
private double height;
public T height(double height) {
this.height = height;
return self();
}
public Rectangle build() {
return new Rectangle(this);
}
}
public static class Builder extends Init<Builder> {
@Override
protected Builder self() {
return this;
}
}
protected Rectangle(Init<?> init) {
super(init);
this.height = init.height;
}
}
To instantiate the Rectangle
:
new Rectangle.Builder().opacity(1.0D).height(1.0D).build();
Again, an abstract Init
class, inheriting from Shape.Init
, and a Build
that is the actual implementation. Each Builder
class implement the self
method, which is responsible to return a correctly cast version of itself.
Shape.Init <-- Shape.Builder
^
|
|
Rectangle.Init <-- Rectangle.Builder
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);
}