How to improve the builder pattern?
Asked Answered
M

10

48

Motivation

Recently I searched for a way to initialize a complex object without passing a lot of parameter to the constructor. I tried it with the builder pattern, but I don't like the fact, that I'm not able to check at compile time if I really set all needed values.

Traditional builder pattern

When I use the builder pattern to create my Complex object, the creation is more "typesafe", because it's easier to see what an argument is used for:

new ComplexBuilder()
        .setFirst( "first" )
        .setSecond( "second" )
        .setThird( "third" )
        ...
        .build();

But now I have the problem, that I can easily miss an important parameter. I can check for it inside the build() method, but that is only at runtime. At compile time there is nothing that warns me, if I missed something.

Enhanced builder pattern

Now my idea was to create a builder, that "reminds" me if I missed a needed parameter. My first try looks like this:

public class Complex {
    private String m_first;
    private String m_second;
    private String m_third;

    private Complex() {}

    public static class ComplexBuilder {
        private Complex m_complex;

        public ComplexBuilder() {
            m_complex = new Complex();
        }

        public Builder2 setFirst( String first ) {
            m_complex.m_first = first;
            return new Builder2();
        }

        public class Builder2 {
            private Builder2() {}
            Builder3 setSecond( String second ) {
                m_complex.m_second = second;
                return new Builder3();
            }
        }

        public class Builder3 {
            private Builder3() {}
            Builder4 setThird( String third ) {
                m_complex.m_third = third;
                return new Builder4();
            }
        }

        public class Builder4 {
            private Builder4() {}
            Complex build() {
                return m_complex;
            }
        }
    }
}

As you can see, each setter of the builder class returns a different internal builder class. Each internal builder class provides exactly one setter method and the last one provides only a build() method.

Now the construction of an object again looks like this:

new ComplexBuilder()
    .setFirst( "first" )
    .setSecond( "second" )
    .setThird( "third" )
    .build();

...but there is no way to forget a needed parameter. The compiler wouldn't accept it.

Optional parameters

If I had optional parameters, I would use the last internal builder class Builder4 to set them like a "traditional" builder does, returning itself.

Questions

  • Is this a well known pattern? Does it have a special name?
  • Do you see any pitfalls?
  • Do you have any ideas to improve the implementation - in the sense of fewer lines of code?
Meathead answered 28/10, 2009 at 17:14 Comment(5)
Your "traditional builder pattern" looks more like what I know as a [fluent interface][1]. When I hear "builder pattern", I think of the [design pattern "builder"][2]. Is it common to refer to a fluent interface as a builder pattern, or am I missing something here? [1]: en.wikipedia.org/wiki/Fluent_interface [2]: en.wikipedia.org/wiki/Builder_patternOwenowena
@Ewan Joshua Blog refers to this pattern as "Builder" (but not as a replacement of the GoF builder). See rwhansen.blogspot.com/2007/07/…. So, I don't know if its good to use the same name, I don't know if it's common, but as a reader of Effective Java, I don't find it cumbersome.Crackbrain
Sounds like you are trying to get the compile times checks you get with constructors. Its a same Java doesn't support named arguments like groovy etc which would solve this problem without an extra builder class.Revell
You can actually do this even better with generics: michid.wordpress.com/2008/08/13/…Nitpicking
It's a clever idea, but you loose a lot of flexibility you get with the traditional builder pattern. Not just in terms of the order in which you specify properties, but also things like referencing the in-progress builder in a variable and setting builder properties asynchronously. You can't do that with your pattern because the type of the builder changes every time you set a property. Really all you've done is made a more verbose constructor, where instead of looking up what each parameter means, you have to look up the correct order of methods. Your IDE could help you with either one.Macey
D
17

No, it's not new. What you're actually doing there is creating a sort of a DSL by extending the standard builder pattern to support branches which is among other things an excellent way to make sure the builder doesn't produce a set of conflicting settings to the actual object.

Personally I think this is a great extension to builder pattern and you can do all sorts of interesting things with it, for example at work we have DSL builders for some of our data integrity tests which allow us to do things like assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();. OK, maybe not the best possible example but I think you get the point.

Deferment answered 28/10, 2009 at 17:20 Comment(0)
W
25

The traditional builder pattern already handles this: simply take the mandatory parameters in the constructor. Of course, nothing prevents a caller from passing null, but neither does your method.

The big problem I see with your method is that you either have a combinatorical explosion of classes with the number of mandatory parameters, or force the user to set the parameters in one particular sqeuence, which is annoying.

Also, it is a lot of additional work.

Whilom answered 28/10, 2009 at 17:23 Comment(5)
The poster specifically mentions trying to avoid passing a large amount of parameters into the constructor, presumably because that doesn't buy you very good compile time checking - since if you are passing in 5 ints, the compiler can't tell you if you've got them in the correct order.Juetta
But there's no compiler on earth that can tell me that I should have typed foo(5, 6) instead of foo(6, 5). The fluent interface proposed in the question makes that (marginally?) less likely but doesn't eliminate the possibility by a long shot.Inflexible
+1. Just yesterday I was reading Item 2 of Effective Java (about using builders instead of telescopic constructors), and it was also recommended there to create a constructor with mandatory params and do necessary validation inside class instance which is being built.Iaea
You should use domain objects instead of 5 and 6.Affirm
"Force the user to set the parameters in one particular sequence" - very good point. I think one of the advantages of the "fluent Builder" or "Bloch Builder" is the ability to set parameters in whatever order you like, and losing it takes you one step backwards towards just a telescoping constructor.Marasco
A
18
public class Complex {
    private final String first;
    private final String second;
    private final String third;

    public static class False {}
    public static class True {}

    public static class Builder<Has1,Has2,Has3> {
        private String first;
        private String second;
        private String third;

        private Builder() {}

        public static Builder<False,False,False> create() {
            return new Builder<>();
        }

        public Builder<True,Has2,Has3> setFirst(String first) {
            this.first = first;
            return (Builder<True,Has2,Has3>)this;
        }

        public Builder<Has1,True,Has3> setSecond(String second) {
            this.second = second;
            return (Builder<Has1,True,Has3>)this;
        }

        public Builder<Has1,Has2,True> setThird(String third) {
            this.third = third;
            return (Builder<Has1,Has2,True>)this;
        }
    }

    public Complex(Builder<True,True,True> builder) {
        first = builder.first;
        second = builder.second;
        third = builder.third;
    }

    public static void test() {
        // Compile Error!
        Complex c1 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2"));

        // Compile Error!
        Complex c2 = new Complex(Complex.Builder.create().setFirst("1").setThird("3"));

        // Works!, all params supplied.
        Complex c3 = new Complex(Complex.Builder.create().setFirst("1").setSecond("2").setThird("3"));
    }
}
Amadus answered 27/7, 2013 at 9:10 Comment(1)
That's a great idea! This way you are not bound to a specific order of setting the parameters but you can still be sure that all parameters are set. Thank you for this inspiring idea!Meathead
D
17

No, it's not new. What you're actually doing there is creating a sort of a DSL by extending the standard builder pattern to support branches which is among other things an excellent way to make sure the builder doesn't produce a set of conflicting settings to the actual object.

Personally I think this is a great extension to builder pattern and you can do all sorts of interesting things with it, for example at work we have DSL builders for some of our data integrity tests which allow us to do things like assertMachine().usesElectricity().and().makesGrindingNoises().whenTurnedOn();. OK, maybe not the best possible example but I think you get the point.

Deferment answered 28/10, 2009 at 17:20 Comment(0)
R
15

Why don't you put "needed" parameters in the builders constructor?

public class Complex
{
....
  public static class ComplexBuilder
  {
     // Required parameters
     private final int required;

     // Optional parameters
     private int optional = 0;

     public ComplexBuilder( int required )
     {
        this.required = required;
     } 

     public Builder setOptional(int optional)
     {
        this.optional = optional;
     }
  }
...
}

This pattern is outlined in Effective Java.

Ramunni answered 28/10, 2009 at 17:17 Comment(1)
Because apparently they are all needed and there are lots of 'em. Though if that's the case, I'd be wondering if there's room for improvement elsewhere.Duce
P
7

Instead of using multiple classes I would just use one class and multiple interfaces. It enforces your syntax without requiring as much typing. It also allows you to see all related code close together which makes it easier to understand what is going on with your code at a larger level.

Priapus answered 7/7, 2011 at 15:48 Comment(0)
P
5

IMHO, this seems bloated. If you have to have all the parameters, pass them in the constructor.

Peper answered 28/10, 2009 at 17:21 Comment(2)
The point of the Builder pattern is that putting them in the constructor is problematic (keeping track of order and such).Tuna
Doesn't the builder pattern separate sequencing from the particular steps such that a 'builder' knows how to execute individual steps and a 'director' class sequences them? That is to say it's a special case of the template pattern.Inflexible
D
5

I've seen/used this:

new ComplexBuilder(requiredvarA, requiedVarB).optional(foo).optional(bar).build();

Then pass these to your object that requires them.

Designed answered 28/10, 2009 at 17:24 Comment(0)
D
2

The Builder Pattern is generally used when you have a lot of optional parameters. If you find you need many required parameters, consider these options first:

  • Your class might be doing too much. Double check that it doesn't violate Single Responsibility Principle. Ask yourself why you need a class with so many required instance variables.
  • You constructor might be doing too much. The job of a constructor is to construct. (They didn't get very creative when they named it ;D ) Just like classes, methods have a Single Responsibility Principle. If your constructor is doing more than just field assignment, you need a good reason to justify that. You might find you need a Factory Method rather than a Builder.
  • Your parameters might be doing too little. Ask yourself if your parameters can be grouped into a small struct (or struct-like object in the case of Java). Don't be afraid to make small classes. If you do find you need to make a struct or small class, don't forget to refactor out functionality that belongs in the struct rather than your larger class.
Demicanton answered 23/2, 2013 at 18:55 Comment(0)
S
1

For more information on when to use the Builder Pattern and its advantages you should check out my post for another similar question here

Silicle answered 23/12, 2009 at 18:42 Comment(0)
P
0

Question 1: Regarding the name of the pattern, I like the name "Step Builder":

Question 2/3: Regarding pitfalls and recommendations, this feels over complicated for most situations.

  • You are enforcing a sequence in how you use your builder which is unusual in my experience. I could see how this would be important in some cases but I've never needed it. For example, I don't see the need to force a sequence here:

    Person.builder().firstName("John").lastName("Doe").build() Person.builder().lastName("Doe").firstName("John").build()

  • However, many times the builder needed to enforce some constraints to prevent bogus objects from being built. Maybe you want to ensure that all required fields are provided or that combinations of fields are valid. I'm guessing this is the real reason you want to introduce sequencing into the building.

    In this case, I like recommendation of Joshua Bloch to do the validation in the build() method. This helps with cross field validation because everything is available at this point. See this answer: https://softwareengineering.stackexchange.com/a/241320

In summary, I wouldn't add any complication to the code just because you are worried about "missing" a call to a builder method. In practice, this is easily caught with a test case. Maybe start with a vanilla Builder and then introduce this if you keep getting bitten by missing method calls.

Paloma answered 5/3, 2015 at 20:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.