Builder Pattern and Inheritance

48,711

Solution 1

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.

Solution 2

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);
}

Solution 3

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

Solution 4

This form seems to nearly work. It is not very tidy but it looks like it avoids your issues:

class Rabbit<B extends Rabbit.Builder<B>> {

    String name;

    public Rabbit(Builder<B> builder) {
        this.name = builder.colour;
    }

    public static class Builder<B extends Rabbit.Builder<B>> {

        protected String colour;

        public B colour(String colour) {
            this.colour = colour;
            return (B)this;
        }

        public Rabbit<B> build () {
            return new Rabbit<>(this);
        }
    }
}

class Lop<B extends Lop.Builder<B>> extends Rabbit<B> {

    float earLength;

    public Lop(Builder<B> builder) {
        super(builder);
        this.earLength = builder.earLength;
    }

    public static class Builder<B extends Lop.Builder<B>> extends Rabbit.Builder<B> {

        protected float earLength;

        public B earLength(float earLength) {
            this.earLength = earLength;
            return (B)this;
        }

        @Override
        public Lop<B> build () {
            return new Lop<>(this);
        }
    }
}

public class Test {

    public void test() {
        Rabbit rabbit = new Rabbit.Builder<>().colour("White").build();
        Lop lop1 = new Lop.Builder<>().earLength(1.4F).colour("Brown").build();
        Lop lop2 = new Lop.Builder<>().colour("Brown").earLength(1.4F).build();
        //Lop.Builder<Lop, Lop.Builder> builder = new Lop.Builder<>();
    }

    public static void main(String args[]) {
        try {
            new Test().test();
        } catch (Throwable t) {
            t.printStackTrace(System.err);
        }
    }
}

Although I have successfully built Rabbit and Lop (in both forms) I cannot at this stage work out how to actually instantiate one of the Builder objects with it's full type.

The essence of this method relies on the cast to (B) in the Builder methods. This allow you to define the type of object and the type of the Builder and retain that within the object while it is constructed.

If anyone could work out the correct syntax for this (which is wrong) I would appreciate it.

Lop.Builder<Lop.Builder> builder = new Lop.Builder<>();

Solution 5

I did some experimenting and I found this to work quite nicely for me. Note that I prefer to create the actual instance at the start and the call all the setters on that instance. This is just a preference.

The main differences with the accepted answer is that

  1. I pass a parameter that indicated the return type
  2. There is no need for an Abstract... and a final builder.
  3. I create a 'newBuilder' convenience method.

The code:

public class MySuper {
    private int superProperty;

    public MySuper() { }

    public void setSuperProperty(int superProperty) {
        this.superProperty = superProperty;
    }

    public static SuperBuilder<? extends MySuper, ? extends SuperBuilder> newBuilder() {
        return new SuperBuilder<>(new MySuper());
    }

    public static class SuperBuilder<R extends MySuper, B extends SuperBuilder<R, B>> {
        private final R mySuper;

        public SuperBuilder(R mySuper) {
            this.mySuper = mySuper;
        }

        public B withSuper(int value) {
            mySuper.setSuperProperty(value);
            return (B) this;
        }

        public R build() {
            return mySuper;
        }
    }
}

and then a subclass look like this:

public class MySub extends MySuper {
    int subProperty;

    public MySub() {
    }

    public void setSubProperty(int subProperty) {
        this.subProperty = subProperty;
    }

    public static SubBuilder<? extends MySub, ? extends SubBuilder> newBuilder() {
        return new SubBuilder(new MySub());
    }

    public static class SubBuilder<R extends MySub, B extends SubBuilder<R, B>>
        extends SuperBuilder<R, B> {

        private final R mySub;

        public SubBuilder(R mySub) {
            super(mySub);
            this.mySub = mySub;
        }

        public B withSub(int value) {
            mySub.setSubProperty(value);
            return (B) this;
        }
    }
}

and a subsub class

public class MySubSub extends MySub {
    private int subSubProperty;

    public MySubSub() {
    }

    public void setSubSubProperty(int subProperty) {
        this.subSubProperty = subProperty;
    }

    public static SubSubBuilder<? extends MySubSub, ? extends SubSubBuilder> newBuilder() {
        return new SubSubBuilder<>(new MySubSub());
    }

    public static class SubSubBuilder<R extends MySubSub, B extends SubSubBuilder<R, B>>
        extends SubBuilder<R, B> {

        private final R mySubSub;

        public SubSubBuilder(R mySub) {
            super(mySub);
            this.mySubSub = mySub;
        }

        public B withSubSub(int value) {
            mySubSub.setSubSubProperty(value);
            return (B)this;
        }
    }

}

To verify it fully works I used this test:

MySubSub subSub = MySubSub
        .newBuilder()
        .withSuper (1)
        .withSub   (2)
        .withSubSub(3)
        .withSub   (2)
        .withSuper (1)
        .withSubSub(3)
        .withSuper (1)
        .withSub   (2)
        .build();
Share:
48,711
Eric Tobias
Author by

Eric Tobias

Updated on March 29, 2021

Comments

  • Eric Tobias
    Eric Tobias about 3 years

    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!

  • Eric Tobias
    Eric Tobias over 10 years
    Thank you, this looks promising. I should be able to test it later today and give some feedback!
  • Eric Tobias
    Eric Tobias over 10 years
    I have implemented a sample test instantiation and it seems to work fine. I can see two drawbacks at the moment. First, for each level of concrete builders I have one additional abstract builder. However, this seems unavoidable. Referring to the abstract classes in the classes' constructor seems fine. One last question, would you say your solution is a natural extension of the CRGP?
  • Eric Tobias
    Eric Tobias over 10 years
    Well, I can use generics. I just can't use the provided solutions that seem to work in the majority of cases! ;)
  • Radiodef
    Radiodef over 10 years
    The extra classes is sort of long-winded but as shown above you can use it as organization by putting the 'build' method only on the concrete class. Putting the build method on the generic class would either mean you'd have a number of methods that uselessly build superclasses or a second convoluted generic system. As far as natural extension, not sure what to say other than that this is probably the extension that's most elegant. What you've encountered is a legitimate issue with the pattern.
  • Eric Tobias
    Eric Tobias over 10 years
    Thank you for your answer. I would suggest you try dropping R respectively L from the builder's declaration. It should still work and solve your problem.
  • Eric Tobias
    Eric Tobias over 10 years
    Thank you, much appreciated!
  • OldCurmudgeon
    OldCurmudgeon over 10 years
    @EricTobias - You are right! Code changed. I still can't work out how to create a Builder and assign it to a variable.
  • Eric Tobias
    Eric Tobias over 10 years
    Your builder is declared to be static, you shouldn't be able to actually instantiate it! ;)
  • OldCurmudgeon
    OldCurmudgeon over 10 years
    You can instantiate static inner classes. static in that case means it has no reference to it's parent class/object.
  • Eric Tobias
    Eric Tobias over 10 years
    True, my bad. As for the syntax, it looks correct. If you want, open a question and post your error message! ;)
  • Visionary Software Solutions
    Visionary Software Solutions almost 9 years
    This seemed like a promising solution, but I get an unchecked cast warning and the bottom leaf node returns Object from my equivalent of GenericMammalBuilder and RabbitBuilder...
  • Visionary Software Solutions
    Visionary Software Solutions almost 9 years
    ...interestingly, if you're implementing a Builder<T> per Effective Java 2, you have to add @Override public abstract T build(). I would've thought the language would infer this.
  • Radiodef
    Radiodef almost 9 years
    @VisionarySoftwareSolutions If you have a GenericRabbitBuilder<T> make sure you have a RabbitBuilder extends GenericRabbitBuilder<RabbitBuilder> and not RabbitBuilder extends GenericRabbitBuilder. The latter is a raw type which is the only thing I can think of which might cause the problem you've described. If that's not the issue, maybe post an Ideone snippet which reproduces the problem. Also, it's normal to get an unchecked warning on return (B) this;.
  • Stan Mots
    Stan Mots about 7 years
    Your implementation is missing the build() method in the child class.
  • Jan Rieke
    Jan Rieke almost 6 years
    BTW: This is also the approach that lombok uses with its the new experimental @SuperBuilder.
  • benez
    benez almost 6 years
    Actually overriding the parent builder setter methods will always work, even without composition. Suclasses are allowed to return a more concrete type. After comparing all these builder implementations, I would suggest to simply override the parent setter methods.
  • jpganz18
    jpganz18 about 5 years
    What happens if MammalBuilder needs to have a specific field? I have tried to add one property for MammalBuilder and crashes when constructing the object... for example, if I add private String description; how can I add it to the MammalBuilder constructor?
  • Radiodef
    Radiodef about 5 years
    @jpganz18 It sounds like there's some other issue, like you're getting an NPE or something. You could try posting a new question with a minimal reproducible example that demonstrates the error you're getting. There shouldn't really be anything special that you need to do to add a field to the subclass builder.
  • benez
    benez about 3 years
    since your generic builder already has a generic field of type R, there is no need to duplicate the field in the subclass builders
  • benez
    benez about 3 years
    unfortunately you also made the Rabbit and Lop class generic, only to support the Builder