4

This is related to the following question:

How to improve the builder pattern?

I'm curious whether it's possible to implement a builder with the following properties:

  1. Some or all parameters are required
  2. No method receives many parameters (i.e., no list of defaults supplied to the initial builder factory method)
  3. All builder fields can be reassigned an arbitrary number of times
  4. The compiler should check that all parameters have been set
  5. It is ok to require that parameters are initially set in some order, but once any parameter is set, all following builders can have this parameter set again (i.e., you can reassign the value of any field of the builder you wish)
  6. No duplicate code should exist for setters (e.g., no overriding setter methods in builder subtypes)

One failed attempt is below (empty private constructors omitted). Consider the following toy builder implementation, and note that line with "Foo f2" has a compiler error because the inherited setter for a returns a BuilderB, not a BuilderFinal. Is there a way to use the java type system to parameterize the return types of the setters to achieve the above goals, or achieve them some other way.

public final class Foo {

    public final int a;
    public final int b;
    public final int c;

    private Foo(
            int a,
            int b,
            int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static BuilderA newBuilder() {
        return new BuilderC();
    }

    public static class BuilderA {
        private volatile int a;

        public BuilderB a(int v) {
            a = v;
            return (BuilderB) this;
        }

        public int a() {
            return a;
        }
    }

    public static class BuilderB extends BuilderA {
        private volatile int b;

        public BuilderC b(int v) {
            b = v;
            return (BuilderC) this;
        }

        public int b() {
            return b;
        }
    }

    public static class BuilderC extends BuilderB {
        private volatile int c;

        public BuilderFinal c(int v) {
            c = v;
            return (BuilderFinal) this;
        }

        public int c() {
            return c;
        }
    }

    public static class BuilderFinal extends BuilderC {

        public Foo build() {
            return new Foo(
                    a(),
                    b(),
                    c());
        }
    }

    public static void main(String[] args) {
        Foo f1 = newBuilder().a(1).b(2).c(3).build();
        Foo f2 = newBuilder().a(1).b(2).c(3).a(4).build();
    }

}
Community
  • 1
  • 1
jonderry
  • 23,013
  • 32
  • 104
  • 171
  • 1
    What if `BuilderC` returns a `BuilderAll` so that parameters can be reassigned arbitrarily? Incidentally the user can cast your builders at will and thereby break the integrity of your builder. – Boris the Spider Jan 18 '14 at 22:58
  • I don't understand the difference between BuilderAll and BuilderFinal. Resilience against casting in client code is just another desirable requirement, but since I don't know how to do even requirements 1-6 without the requirement to be resilient against clients performing casting, I omitted it. – jonderry Jan 18 '14 at 23:42
  • By the way, if there is a solution to this, I expect it would probably need to use casting internally since this is a mutable builder object. My initial idea was to use a type parameter to store the return type of the setter so that a BuilderA could return, for example, a BuilderC when its setter was called if the object had already had the setter for b invoked. However, this approach seems to fail because the type parameter is recursive. Is there such a way for subclasses to inherit methods and return values of their own type from such methods without overriding? – jonderry Jan 19 '14 at 00:01
  • Just curious, what's the point of allowing parameters to be set multiple times? – Radiodef Jan 19 '14 at 01:14
  • @jonderry I undid the removal of my post as I updated the code and my post to showcase a generic version of builder inheritance which returns the correct type (instead of the base-type) – Roman Vottner Jan 19 '14 at 02:12
  • @Radiodef, for example, you may have a config builder class that has many configs that must be set before a valid config is built. You many also want to dynamically update configs at runtime while allowing the creation of immutable config snapshots by calling build. Checking for the mandatory configs would ideally be done by the compiler if possible. – jonderry Jan 19 '14 at 02:25
  • So put more simply you are wanting to use a single builder instance to build multiple objects. – Radiodef Jan 19 '14 at 02:27

5 Answers5

3

Your requirements are really hard, but see if this generic solution fits the bill:

public final class Foo {

    public final int a;
    public final int b;
    public final int c;

    private Foo(
            int a,
            int b,
            int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static BuilderA<? extends BuilderB<?>> newBuilder() {
        return new BuilderFinal();
    }

    public static class BuilderA<T extends BuilderB<?>> {
        private volatile int a;

        @SuppressWarnings("unchecked")
        public T a(int v) {
            a = v;
            return (T) this;
        }

        public int a() {
            return a;
        }
    }

    public static class BuilderB<T extends BuilderC<?>> extends BuilderA<T> {
        private volatile int b;

        @SuppressWarnings("unchecked")
        public T b(int v) {
            b = v;
            return (T) this;
        }

        public int b() {
            return b;
        }
    }

    public static class BuilderC<T extends BuilderFinal> extends BuilderB<T> {
        private volatile int c;

        @SuppressWarnings("unchecked")
        public T c(int v) {
            c = v;
            return (T) this;
        }

        public int c() {
            return c;
        }
    }

    public static class BuilderFinal extends BuilderC<BuilderFinal> {

        public Foo build() {
            return new Foo(
                    a(),
                    b(),
                    c());
        }
    }

    public static void main(String[] args) {
        Foo f1 = newBuilder().a(1).b(2).c(3).build();
        Foo f2 = newBuilder().a(1).b(2).c(3).a(4).build();
    }

}
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • `newBuilder` can return a `BuilderA>`. – Radiodef Jan 19 '14 at 01:59
  • 1
    And this is the solution. I was working on something similar but you got to it before me. This works because all the methods return "capture of" until you get to BuilderFinal. Then all the methods return a BuilderFinal. – Radiodef Jan 19 '14 at 02:39
  • 1
    Very nice! This is what I was trying to get originally, but I didn't know how to use the wildcards like that to avoid the type parameter recursion problems. – jonderry Jan 19 '14 at 02:50
  • Actually there is a bug in this. Consider the following: "Foo f = newBuilder().a(4).a(5).c(34).build()". – jonderry Jan 19 '14 at 03:08
  • I think you should remove requirements 3 and 5, which would make it easier. Why would you want the values to be reassigned anyway? – Jordão Jan 19 '14 at 04:20
  • 1
    There's a comment to the question about it. The reason is that you may want to build multiple objects with the same builder, for example if it's a config builder and you want to generate a series of snapshots and ensure that some minimum set of configs are set with at least some value. Passing such values to a builder factory method is an option, but you may pull the configs in different parts of the code or there may be so many that that you want a builder for the arg list of the factory method for the builder. You could check them at runtime, but it's better to have the compiler catch it. – jonderry Jan 19 '14 at 04:50
  • ... I added another crazy answer, take a look! – Jordão Jan 19 '14 at 04:52
  • .... the downcasts work because `BuilderFinal` can be cast to any `BuilderX inherits BuilderX>` class. – Jordão Jan 19 '14 at 13:03
  • OK - I was wrong! Sorry, this code works - for some reason I needed to clean the project again ... – Roman Vottner Jan 19 '14 at 13:58
2

To my knowledge the builder pattern should be used in case multiple parameters are used that make the invocation rather complicated as parameters might swap positions or not make it obviously clear what which parameter is for.

A rule of thumb would be to require compulsory parameters within the constructor of the builder and optional parameters within the methods. However, often more than 4 parameters may be required which makes the invocation again rather unclear and the pattern redundant. So a split up into default constructor and parameter setting for each parameter can also be used.

The checks should happen in a own method which is invoked within the build-method so you could invoke it using super. Compile-time security is only guaranteed on the correct data types (only exception - null is possible to, this has to be fetched within the checkParameters()-method). You can however force that all required parameters are set on requiring them within the Builder constructor - but as mentioned before, this may lead to a redundant pattern.

import java.util.ArrayList;
import java.util.List;

public class C
{
    public static class Builder<T extends C, B extends C.Builder<? extends C,? extends B>> extends AbstractBuilder<C>
    {
        protected String comp1;
        protected String comp2;
        protected int comp3;
        protected int comp4;
        protected int comp5;
        protected List<Object> comp6 = new ArrayList<>();
        protected String optional1;
        protected List<Object> optional2 = new ArrayList<>();

        public Builder()
        {

        }

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

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

        public B withComp3(int comp3)
        {
            this.comp3 = comp3;
            return (B)this;
        }

        public B withComp4(int comp4)
        {
            this.comp4 = comp4;
            return (B)this;
        }

        public B withComp5(int comp5)
        {
            this.comp5 = comp5;
            return (B)this;
        }

        public B withComp6(Object comp6)
        {
            this.comp6.add(comp6);
            return (B)this;
        }

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

        public B withOptional2(Object optional2)
        {
            this.optional2.add(optional2);
            return (B)this;
        }

        @Override
        protected void checkParameters() throws BuildException
        {
            if (this.comp1 == null)
                throw new BuildException("Comp1 violates the rules");
            if (this.comp2 == null)
                throw new BuildException("Comp2 violates the rules");
            if (this.comp3 == 0)
                throw new BuildException("Comp3 violates the rules");
            if (this.comp4 == 0)
                throw new BuildException("Comp4 violates the rules");
            if (this.comp5 == 0)
                throw new BuildException("Comp5 violates the rules");
            if (this.comp6 == null)
                throw new BuildException("Comp6 violates the rules");
        }

        @Override
        public T build() throws BuildException
        {
            this.checkParameters();

            C c = new C(this.comp1, this.comp2,this.comp3, this.comp4, this.comp5, this.comp6);
            c.setOptional1(this.optional1);
            c.setOptional2(this.optional2);
            return (T)c;
        }
    }

    private final String comp1;
    private final String comp2;
    private final int comp3;
    private final int comp4;
    private final int comp5;
    private final List<?> comp6;
    private String optional1;
    private List<?> optional2;

    protected C(String comp1, String comp2, int comp3, int comp4, int comp5, List<?> comp6)
    {
        this.comp1 = comp1;
        this.comp2 = comp2;
        this.comp3 = comp3;
        this.comp4 = comp4;
        this.comp5 = comp5;
        this.comp6 = comp6;
    }

    public void setOptional1(String optional1)
    {
        this.optional1 = optional1;
    }

    public void setOptional2(List<?> optional2)
    {
        this.optional2 = optional2;
    }

    // further methods omitted

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(this.comp1);
        sb.append(", ");
        sb.append(this.comp2);
        sb.append(", ");
        sb.append(this.comp3);
        sb.append(", ");
        sb.append(this.comp4);
        sb.append(", ");
        sb.append(this.comp5);
        sb.append(", ");
        sb.append(this.comp6);

        return sb.toString();
    }
}

On extending D from C and also the builder, you need to override the checkParameters() and build() method. Due to the use of Generics the correct type will be return on invoking build()

import java.util.List;

public class D extends C
{
    public static class Builder<T extends D, B extends D.Builder<? extends D, ? extends B>> extends C.Builder<D, Builder<D, B>>
    {
        protected String comp7;

        public Builder()
        {

        }

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

        @Override
        public void checkParameters() throws BuildException
        {
            super.checkParameters();

            if (comp7 == null)
                throw new BuildException("Comp7 violates the rules");
        }

        @Override
        public T build() throws BuildException
        {
            this.checkParameters();

            D d = new D(this.comp1, this.comp2, this.comp3, this.comp4, this.comp5, this.comp6, this.comp7);

            if (this.optional1 != null)
                d.setOptional1(optional1);
            if (this.optional2 != null)
                d.setOptional2(optional2);

            return (T)d;
        }
    }

    protected String comp7;

    protected D(String comp1, String comp2, int comp3, int comp4, int comp5, List<?> comp6, String comp7)
    {
        super(comp1, comp2, comp3, comp4, comp5, comp6);
        this.comp7 = comp7;
    }

    @Override
    public String toString()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString());
        sb.append(", ");
        sb.append(this.comp7);
        return sb.toString();
    }
}

The abstract builder class is quite simple:

public abstract class AbstractBuilder<T>
{
    protected abstract void checkParameters() throws BuildException;

    public abstract <T> T build() throws BuildException;
}

The exception is simple too:

public class BuildException extends Exception
{
    public BuildException(String msg)
    {
        super(msg);
    }
}

And last but not least the main method:

public static void main(String ... args)
{
    try
    {
        C c = new C.Builder<>().withComp1("a1").withComp2("a2").withComp3(1)
            .withComp4(4).withComp5(5).withComp6("lala").build();
        System.out.println("c: " + c);

        D d = new D.Builder<>().withComp1("d1").withComp2("d2").withComp3(3)
            .withComp4(4).withComp5(5).withComp6("lala").withComp7("d7").build();
        System.out.println("d: " + d);

        C c2 = new C.Builder<>().withComp1("a1").withComp3(1)
            .withComp4(4).withComp5(5).withComp6("lala").build();
        System.out.println(c2);
    }
    catch (Exception e)
    {
        e.printStackTrace();
    }
}

Output:

c: a1, a2, 1, 4, 5, [lala]
d: d1, d2, 3, 4, 5, [lala], d7
Builders.BuildException: Comp2 violates the rules
        ... // StackTrace omitted

Though, before messing to much with Generics I'd suggest to stick to the KISS policy and forget inheritance for builders and code them simple and stupid (with part of them including dumb copy&paste)


@edit: OK, after all the work done and re-reading the OP as well as the linked post I had a totally wrong assumption of the requirements - like a German wording says: "Operation successful, patient is dead" - though I leave this post here in case someone wants a copy&paste like solution for a builder-inheritance which actually returns the correct type instead of the the base type

Roman Vottner
  • 12,213
  • 5
  • 46
  • 63
  • Having a constructor with many parameters violates requirement 2 (I was assuming the initial builder was constructed via a factory method with zero or one parameters). Requirement 5 is the problem with my code, which satisfies 1, 2, 3, 4, and 6. – jonderry Jan 19 '14 at 00:09
  • that's why I've included the default constructor. Can you explain 5) with some examples? Either it is too late or I just don't get it (or a combination of both :P) – Roman Vottner Jan 19 '14 at 00:15
  • A way to achieve 1-4 and 6 is to use a series of types, each guaranteed to have one additional setter called on it because the only way to construct an instance of each type is to call the setter on the "previous" type. However, if you go back and call an earlier setter, the compiler will no longer "remember" that the previous values have already been set because the compile-time type of the object reverts to the earlier version. Try pasting my code and seeing what happens with f1 and f2. – jonderry Jan 19 '14 at 00:41
1

I had a crazy idea once, and it kind of goes against some of your requirements, but I think you can have the builder constructor take the required parameters, but in a way that makes it still clear which parameters are being set. Take a look:

package myapp;

public final class Foo {

  public final int a;
  public final int b;
  public final int c;

  private Foo(int a, int b, int c) {
      this.a = a;
      this.b = b;
      this.c = c;
  }

  public static class Builder {
    private int a;
    private int b;
    private int c;

    public Builder(A a, B b, C c) {
      this.a = a.v;
      this.b = b.v;
      this.c = c.v;
    }

    public Builder a(int v) { a = v; return this; }
    public Builder b(int v) { b = v; return this; }
    public Builder c(int v) { c = v; return this; }

    public Foo build() {
      return new Foo(a, b, c);
    }
  }

  private static class V {
    int v;
    V(int v) { this.v = v; }
  }
  public static class A extends V { A(int v) { super(v); } }
  public static class B extends V { B(int v) { super(v); } }
  public static class C extends V { C(int v) { super(v); } }
  public static A a(int v) { return new A(v); }
  public static B b(int v) { return new B(v); }
  public static C c(int v) { return new C(v); }

  public static void main(String[] args) {
    Foo f1 = new Builder(a(1), b(2), c(3)).build();
    Foo f2 = new Builder(a(1), b(2), c(3)).a(4).build();
  }

}

For other clients, static imports are your friends:

package myotherapp;

import myapp.Foo;
import static myapp.Foo.*;

public class Program {

  public static void main(String[] args) {
    Foo f1 = new Builder(a(1), b(2), c(3)).build();
    Foo f2 = new Builder(a(1), b(2), c(3)).a(4).build();
  }

}
Community
  • 1
  • 1
Jordão
  • 55,340
  • 13
  • 112
  • 144
0

Building on Jordão's idea, I came up with the following, which may arguably satisfy all requirements 1-6 even though there is some duplicate code in the type parameters. Essentially, the idea is to "pass around" the return types of each method by using type parameters to override the return value of the inherited methods. Even though the code is verbose and impractical, and actually requires Omega(n^3) characters if you extend it out to an arbitrary number of fields n, I'm posting it because I think it's an interesting use of the java type system. If anyone can find a way to reduce the number of type parameters (especially asymptotically), please post in the comments or write another answer.

public final class Foo {

    public final int a;
    public final int b;
    public final int c;

    private Foo(
            int a,
            int b,
            int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    public static BuilderA<? extends BuilderB<?, ?>, ? extends BuilderC<?, ?>> newBuilder() {
        return new BuilderFinal();
    }

    public static class BuilderA<B extends BuilderB<?, ?>, C extends BuilderC<?, ?>> {
        private volatile int a;

        @SuppressWarnings("unchecked")
        public B a(int v) {
            a = v;
            return (B) this;
        }

        public int a() {
            return a;
        }
    }

    public static class BuilderB<B extends BuilderB<?, ?>, C extends BuilderC<?, ?>> extends BuilderA<B, C> {
        private volatile int b;

        @SuppressWarnings("unchecked")
        public C b(int v) {
            b = v;
            return (C) this;
        }

        public int b() {
            return b;
        }
    }

    public static class BuilderC<B extends BuilderC<?, ?>, C extends BuilderC<?, ?>> extends BuilderB<B, C> {
        private volatile int c;

        @SuppressWarnings("unchecked")
        public BuilderFinal c(int v) {
            c = v;
            return (BuilderFinal) this;
        }

        public int c() {
            return c;
        }
    }

    public static class BuilderFinal extends BuilderC<BuilderFinal, BuilderFinal> {

        public Foo build() {
            return new Foo(
                    a(),
                    b(),
                    c());
        }
    }

    public static void main(String[] args) {
        Foo f1 = newBuilder().a(1).b(2).c(3).a(2).build();
        Foo f2 = newBuilder().a(1).a(2).c(3).build(); // compile error
        Foo f3 = newBuilder().a(1).b(2).a(3).b(4).b(5).build(); // compile error
    }

}
jonderry
  • 23,013
  • 32
  • 104
  • 171
-1

Why don't you want to override the setters in BuilderFinal? They would just need to downcast the super method:

public static class BuilderFinal extends BuilderC {

    @Override
    public BuilderFinal a(int v) {
        return (BuilderFinal) super.a(v);
    }

    @Override
    public BuilderFinal b(int v) {
        return (BuilderFinal) super.b(v);
    }

    public Foo build() {
        return new Foo(
                a(),
                b(),
                c());
    }
}
Ozan
  • 4,345
  • 2
  • 23
  • 35