How to improve the builder pattern?

荒凉一梦 提交于 2019-11-27 11:13: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.

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.

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

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.

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.

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

reccles

I've seen/used this:

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

Then pass these to your object that requires them.

Eva

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.
Aaron

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

brariden

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!