0

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

Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124
  • 1
    AFAIK, the proper way is to put required arguments in the constructor, like in `new FooBuilder(required1, required2).bar(optional).build();` –  Nov 01 '17 at 12:21
  • 1
    The main idea, as you already know, is that the build method will return a fully constructed _valid_ instance of the object. In my opinion, you should call the private constructor of the wrapped object inside the builder and pass in all the required arguments as parameters to the constructor. The way I am following is as shown in the "Effective Java" book. – hovanessyan Nov 01 '17 at 12:26
  • I agree actually, the full constructor way is less likely to be accidentally broken than either of the others (including the one I said I preferred) – Nick Cardoso Nov 01 '17 at 12:28
  • 1
    The first and second one are basically equivalent. Passing just the builder is simpler, because it avoids having a constructor withe a large number of arguments. It's also the one used in Effective Java (see http://www.informit.com/articles/article.aspx?p=1216151&seqNum=2). The third one doesn't allow making the class immutable, which is often the main reason why a builder is used. – JB Nizet Nov 01 '17 at 12:37
  • Thanks for that @JBNizet, that would make a good answer - one problem I can see though (with passing builder) is no compile time error if you break the connection (someone adds a field to the class, but doesn't update the builder/constructor) – Nick Cardoso Nov 01 '17 at 12:54
  • Sure. But you could also forget to add an argument to the constructor. The builder and the class it builds form a single encapsulated unit. The probability of a bug always exists, but it's low, unless you really don't understand what you're doing, since they're both in the same file, and the compiler will force you to initialize the field if it's final. – JB Nizet Nov 01 '17 at 12:58
  • 1
    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 due to an uninitialized field. Conversely, adding a field to the builder but not the built class would produce a warning for dead code. – jaco0646 Nov 02 '17 at 23:51
  • 1
    Not an answer to your question, but a potentially interesting counterposition on using the Builder pattern: [Design Patterns and Anti-Patterns, Love and Hate](http://www.yegor256.com/2016/02/03/design-patterns-and-anti-patterns.html) – Phil Brubaker Nov 04 '17 at 12:44

2 Answers2

1

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.

Rob
  • 11,446
  • 7
  • 39
  • 57
  • This is a little bit like a long, unclear comment right now. How about adding a code example of what you consider the one-true-builder to be? (or both that you've mentioned). Each of the examples I've used would have *methods* (hence the 'accessors' placeholder - they were excluded for brevity because that much should be obvious to any reader interested in this question) - The answer you have referenced is just the second example I gave. – Nick Cardoso Nov 09 '17 at 15:15
  • With examples this could be a reasonable answer - I can't see any merit to "The idea that it's done to prevent a lot of overloaded constructors doesn't make much sense" though. Why do you surmise the builder pattern exists? – Nick Cardoso Nov 09 '17 at 15:16
  • Did you look at the link?? That's exactly what my answer is saying, the link has the actual Bloch pattern. As to the first one, the one from the Gang of Four, they have examples in the book, but that is not the Builder you are using. I mentioned it because it was around a long time before the Bloch one so if you are going to ask about reasons for Builders, the original merits a mention. Look at the link and contrast it to your examples. – Rob Nov 09 '17 at 15:24
  • You clearly haven't read my reply here. Your 'bloch pattern' is exactly what is detailed as the second example in the question. Very mature downvoting everything. – Nick Cardoso Nov 09 '17 at 16:09
  • No none of your 'examples' is the Bloch Pattern. Go see it at the link I included. Show me where any of your examples return the Builder. Seriously, you are hilarious.... – Rob Nov 09 '17 at 16:10
  • If you can't understand brevity like all the others who left comments did, that really is your own problem. – Nick Cardoso Nov 09 '17 at 16:11
0

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

Nick Cardoso
  • 20,807
  • 14
  • 73
  • 124