Main Points
- use builder design pattern when the constructor is very much populated by arguments.
- it becomes tedious for creating new instances of a class if constructor is accepting so much arguments.
example
constructor over population
public class Burger {
private int size;
private boolean cheese = false;
private boolean tomato = false;
private boolean lettuce = false;
private boolean pepperoni = false;
public Burger(int size, boolean cheese, boolean tomato, boolean lettuce, boolean pepperoni) {
this.size = size;
this.cheese = cheese;
this.tomato = tomato;
this.lettuce = lettuce;
this.pepperoni = pepperoni;
}
}
Burger burger = new Burger(14, true, true, false, false);
As it can be seen, as the number of items to be added to the burger grows, we have to increase the number of arguments in the constructor too.
This is a very bad design practice because it becomes difficult to remember the order of arguments as well as what type of arguments are there in the arguments.
builder pattern
public class Burger {
private int size;
private boolean cheese = false;
private boolean pepperoni = false;
private boolean lettuce = false;
private boolean tomato = false;
public Burger(BurgerBuilder builder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.lettuce = builder.lettuce;
this.tomato = builder.tomato;
}
}
public class BurgerBuilder {
public int size;
public boolean cheese = false;
public boolean pepperoni = false;
public boolean lettuce = false;
public boolean tomato = false;
public BurgerBuilder(int size) {
this.size = size;
}
public BurgerBuilder addPepperoni() {
this.pepperoni = true;
return this;
}
public BurgerBuilder addLettuce() {
this.lettuce = true;
return this;
}
public BurgerBuilder addCheese() {
this.cheese = true;
return this;
}
public BurgerBuilder addTomato() {
this.tomato = true;
return this;
}
public Burger build() {
return new Burger(this);
}
}
Burger burger = (new BurgerBuilder(14))
.addPepperoni()
.addLettuce()
.addTomato()
.build();
In the BurgerBuilder
class, all the add
method returns the builder type so that we can form the chain for subsequent addition.
Finally, build
method returns the Burger which is what we want.
Make sure you implement this design pattern according to the context otherwise it will be an overkill. :D
Top comments (37)
I always wonder: why the need of an extra builder? Wouldn't it be sufficient to just add the "withTomato()" etc calls to the burger itself?
The builder pattern is very common when you use immutable value objects and you don't want any change to happen after object creation.
This example doesn't show it, but I would personally make all Burger attributes final, the BurgerBuilder an static inner class of Burger and the Burger(BurgerBuilder) constructor private.
I've been learning about patterns for the last while, I have one thought on the static implementation. Making the builder class static could lead to concurrency issues.
No, just from the static keyword on a class you can't tell whether the class is thread safe or not.
The static keyword on a enclosed class means that the class is treated like a top-level class. It can be instantiated independently from the outer class and has no reference to an instance of the outer class.
A "real" non-static inner class can only be instantiated with an instance of the outer class and it will have access to the members of the outer class. This would not work for a Builder.
See also Java Language Specification §8.5.1:
docs.oracle.com/javase/specs/jls/s...
There could be concurrency issues if you would share the Builder instance with other threads. But this is independent from the Builder being static or not and it would be a quite unusual case to access the builder concurrently.
Thanks Gregor,
In my case, the builder would be shared between threads so I could see the concurrency issue of using static. Automated user acceptance tests which execute in parallel in the same jvm.
I have the choice of making the builder thread safe or blocking users from instantiating the object without that builder.
Like I said making the builder a static inner class or a top-level class makes no difference regarding concurrency.
Each thread creates its own Builder object that is not shared between threads. Each thread fills it individually and calls build(). Afterwards you have a perfectly thread safe immutable object that you can make available to multiple thread.
The Builder object itself is not thread safe. But it doesn't need to be, since it should not be shared between threads. Sharing the Builder object between threads circumvents the whole point of this pattern and I would say its quite bad design.
So this is thread safe unless you use it in a unsafe way.
I agree, the internals of the class (i.e. non final fields) would determine if it's thread safe or not.
I agree, in the case where the builder is static is not thread safe. Why shouldn't it be shared between threads though? My understanding of this pattern is that it's about object creation, in a way that makes the code more maintainable and reduces the chances of objects with incorrect state. I haven't seen any mention of concurrecny.
I've put 3 possible builder implementations in my pattern-play repo, as you may have guessed I use it for learning patterns
github.com/ethomev/pattern-play/tr...
I like the pattern where there is only one constructor for the object and it takes the builder. I think I will use this implementation the next time I need to use the builder pattern.
P.S. Sorry for the delay in replying, I'm now a father of 2 so I have less free time ;-)
Yeah. That will be more concrete. The tutorial is just a rough pattern to get the insights.
Good point. I had an other case in mind were the objects are created in different ways and may get additional fields set at a later stage.
Thank you for the article.
I want to add couple-o-things. I'm sorry if I sound pushy, please feel free to parry my notes and point my mistakes.
Please, oh please, do use named arguments for vague stuff (concerning methods as well):
Remember about default values, which is a thing in C# and Kotlin. Sorry, Java.
Also you can place these setters inside original Burger class. Unfortunately, this way object initialization wouldn't be atomic anymore.
I just checked google, so I wouldn't suggest using double braces initialization for Java. C# initializers are OK, though, and atomic. I do not know of the same stuff for Kotlin, but its syntax for constructors is great in the first place.
Basically, the problem grows out of Java itself, because I see more elegant solutions in other languages like C# and Kotlin. At least more complex example required in order to favour builders and factories.
If you don't need your properties to be necessarily set at creation, you can also set properties when constructing the object in C# even if they're not in the constructor.
True that. It'd be much easier if there was a concept like named argument in languages like Java.
I'm a huge fan of returning
this
with java builder patterns. The fluent api you get out of it is fantastic. You can very nearly get something like a custom DSL out of just that technique aloneTrue that. The chain rules are elegant and are more aesthetic in builder patterns.
Aren't you duplicating the set of Boolean parameters? Why not create an instance of Burger in the BurgerBuilder constructor and return it when calling build().
Oh, I see, that way Burger won't be immutable.
Yeah. The whole point is to avoid the constructor of Burger being populated. This way, it will allow you to create chained calls to the builder and yes the side effect is the immutability of Burger subsequently.
Just use Lombok.
that example is so OOP overkill that i don't know where to start
first:
instead of
1 constructor VS 1 constructor + 4 methods call, what's the gain ?
second:
you got 2 objects creations instead of 1
what's the gain ?
third: immutability
is already immutable and the builder is the one thas is not with the added methods
what are the "addX" methods for ? if immutability is the goal ?
this example fails to display the purpose of the builder pattern IMHO
for another idea of implementation:
with size and ingredients being higher representation (Dict/Set/HashMap)for example:
size can be a set of "big, small, medium"
ingredients can be "pepperonni: true" or "cheese: new Cheese("gouda")"
We have to kill this trend to make code for making code and remember that code is a higher level of expressing a solution/process.
I wrote the burger builder in my own preferred pattern. dev.to/jtlunsford/build-a-differen... Didn't care much for the chaining though I do find chaining nice if I need to pass the object around my application before we call it finished
Cool. Yeah, it's all about the use cases in the end.
A cool way of using builder design pattern when you want mandatory parameters:
I prefer Java 8 style builders
You can easily create an overload which accepts only the mandatory arguments
The configuration is implemented as a Consumer of type T, where T is a some sort of configuration object.
See Molten JSON for a JSON Builder implemented that way.
I just realised I've never really understood it before. I think the difference comes from the pragmatic use case, aka "the constructor has loads of arguments" + a nice a short none specific example. I'd like to read something this clear for every design pattern.
Thanks. I might do similar post for other patterns I havebeen using.