简体   繁体   中英

What is the wrong/right way to implement the Builder pattern?

My understanding is that the builder pattern exists to avoid multiple overloaded constructors (for classes more complex than my example)

public class Example {

    private String a,b,c;

    public Example() {
       //setup defaults
    }

    public Example(String a) {
       this.a=a;
       //setup defaults
    }

    public Example(String a, String b) {
       this.a=a;
       this.b=b;
       //setup defaults
    }

    public Example(String a, String b, String c) {
       this.a=a;
       this.b=b;
       this.c=c;
    }

}

But when switching to a builder, which of the following is the correct approach to take?

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //we setup defaults through getters
            //and example only has the 'full' constructor
            return new Example(getA(), getB(), getC()); 
        }

    }

}

OR

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //pass in the builder and let 'Example' care about defaults
            return new Example(this); 
        }

    }

}

OR

public class Example {

    public static class Builder {

        //accessors

        public Example build() {
            //only empty constructor exists which sets all defaults
            //access fields directly to override defaults
            Example e = new Example(); 
            e.a = a;
            e.b = b;
            e.c = c;
            return e;
        }

    }

}

Are any of these breaking the builder pattern? Is there a canonically correct approach?

(I want to note that neither Oracle nor Google's documents on conventions covered this)

I know this similar question was asked but as far as I can tell (despite the name) that question only covers an actual Builder pattern vs a non-builder pattern.

I prefer the 3rd approach but many of the examples I find are using the approach of passing the builder into the constructor. I don't know if I am missing some advantage / potential problems

I am sure my answer will neither be popular nor the chosen one, but I have been obsessed with builders for a long time.

First, there are 2 Builder Patterns. The one in the vaunted Gang of Four book, and the chaining, often embedded constructor replacement.

For the first one, we don't have to speculate, the book makes it very clear: the Builder Pattern is a Creational Pattern for things whose construction is done in steps, or parts. The idea is you have a Director that you deal with and the Director then uses one of a number of Builders to construct the product. You are hiding the construction details from the consumers.

In the other case, the classic example is the Bloch Static Builder from the 2nd Edition of Effective Java. The purposes there are:

  • immutability: you can make things that have a lot of attributes where most of them are immutable
  • Java has no named arguments, construction of complex things is much more readable in a builder
  • If you want you can also add some construction logic, so for instance, suppose you have 5 params, and a few are required, and a few are not, you can embed that logic in the build() method.

But the most important thing to note about this one is that none of the examples you have above is correct. Look at the selected answer on this question: How to use Builder pattern as described by Joshua Bloch's version in my ModelInput class? . Notice, the static builder has methods for each of the params, and then returns an instance of itself. That's mandatory for the chaining to work, you cannot just assign values.

I read that there's a 3rd edition coming of Effective Java . One of the best if not the best book on Java.

The idea that it's done to prevent a lot of overloaded constructors doesn't make much sense, unless the question is confined to the use of the 2nd one in a language that does not support defaults for function parameters.

Based on other questions answers, many blogs and the comments here - the collective answer seems to be:

A correctly implemented Builder pattern just means we can produce complete objects without having to rely on multiple constructor overloads and passing nulls

The general consensus is that the 3rd method outlined in the question would be incorrect, while the 2nd approach can be considered correct only if the resulting object has final fields (immutability which has the benefit of causing compile time errors if broken) and the first approach is correct in that only one constructor is required (while the builder still allows you to provide only part of the data to receive a complete object)


In case the comments get removed, I am adding quotes from relevant comments/feedback here:

AFAIK, the proper way is to put required arguments in the constructor - RC

The main idea, as you already know, is that the build method will return a fully constructed valid instance of the object ... - hovanessyan

Passing just the builder is simpler, because it avoids having a constructor withe a large number of arguments ... the compiler will force you to initialize the field if it's final ... The third one doesn't allow making the class immutable, which is often the main reason why a builder is used - JB Nizet

Since the built class is immutable, its fields are all final and therefore adding a field to the class but not the builder would produce a compile error - jaco0646

There are some more interesting answers on this related question about whether Builder has to be a static inner class

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM