The Builder Design Pattern
What Problem Does a Builder Solve?
The builder pattern seeks to solve the issue of oversized constructors when an object has many mandatory instance variables. When this is the case you often have to take time to study the constructor when trying to figure out what parameters go in what order. Additionally if you have sequences of identically typed parameters (e.g four String objects in a row) it can lead to very subtle bugs because the compiler will not complain about the erroneous positioning because all it knows is it has values of the correct type.
Consider the example object below meant to denote a person. When trying to declare a new instance of type Person you need to know the ordering of the names as well as the ordering of the weight and height. Looking at the example declaration you can see it is very difficult to infer which of the two numbers, 80.0 or 73.0, corresponds to the weight or height.
Additionally consider a person with the name "John Joe James", and yes that's the name of someone I knew in high school. With a name like that which is the first name, middle name and last name? It would be extremely hard to infer this from a simple constructor without having to take the extra time to examine the source code for the Person constructor.
Now imagine the example of a class meant to represent the data on a nutritional label for food, you might have to have numeric values for 20 or more nutrients such as protein as well as the calories and other information such as allergy warnings. With that much required information it becomes inevitable that a mistake is going to happen at least once when declaring an instance using a 20 or more parameter constructor, especially when so many are going to be simple numbers.
One of the common fixes for this problem developers try and use is to declare the Person object using an empty constructor and then call all the setters on the object immediately afterwards. This does solve the issues of having to know the order of the parameters and an example is given below.
This is a bad idea because it means during the chain of setters the object may be in an inconsistent state but can still actually be used. If I accidentally added a call to another method halfway through the list of setters it could have unintended consequences. We want out object to be in a completely valid state when we create it. This also has implications for concurrent code where two or more threads may be operating on a given object, what if thread A required a value to be set that thread B hasn't actually set yet? Failure, that's what.
The builder pattern offers the best of both worlds; giving you a complete object upon return (as long as all the fields are set) as well as preventing you from having to remember the position of different arguments. Let's dive in to an example.
Consider a builder when faced with many constructor parameters.— Effective Java by Joshua Bloch
Builder Pattern Example
Below you will see the code required to create a builder for the Person object. All the arguments for the sake of this example have been made to be optional however making some arguments mandatory is easy by creating a constructor for the Builder object with the mandatory arguments and having them as parameters for the static builder() method. This exposes a weakness in the builder pattern; it scales well to many optional arguments and does not scale well with mandatory arguments.
You will notice our class declaration for the Person class just got a lot bigger, and now anytime we add a new instance variable to Person (for example adding shoe size) then the builder also needs to be updated. This is another weakness of the builder pattern, it requires boilerplate code and maintenance of the boilerplate. However later in this article I will show you how to turn all that boilerplate into one single annotation that handles maintenance for you.
As you can see below the builder pattern does allow for much nicer object declarations where every arguments name is explicitly given massively reducing the chances of accidental positioning mistakes leading to hard to find bugs.
Arguably the object declarations are also more pleasing to the eye of the reader, they flow much nicer than long constructors.
This shows of the biggest strength of the builder pattern, it allows explicit parameter setting so the reader never needs to go to the source code of the Person class to examine the constructor. Although you spent some extra time writing the builder implementation it saves countless hours down the line when you don't have to search for subtle bugs caused by swapped parameters.
Advantages of the Builder Pattern
- No more worrying about what argument goes in what position, the builder pattern is verbose enough to ensure you always know what argument you are working on.
- Perfect for when an object has a lot of optional arguments.
Disadvantages of the Builder Pattern
- Doesn't scale well to lot's of mandatory arguments.
- Lot's of boilerplate code needed for implementation that needs updated anytime the original object is updated.
Reducing the Boilerplate Code
The builder pattern is perfect for making objects with lots of instance variables easier to construct however it does come with quite a lot of boilerplate code. This boilerplate not only makes the object definition more crowded and harder to read through but it also needs to be maintained, if an instance variable is added or removed then the builder also needs to be updated to reflect that change. Wouldn't it be nice if there was a way to remove the boilerplate AND automatically update the builder based on the instance variables? Lombok provides exact that with the @Builder annotation, my introduction to Lombok article covers this and many more of the details.
- Introduction to Lombok Part One
Everyone hates boilerplate code, especially in Java. This articles shows you how to cut down repetitive constructors, getters and setters using the annotation based Lombok library.
Builder Design Pattern Video Tutorial
Today we looked at how the builder pattern can help reduce the bugs and eye-hurting code associated with oversized constructors. We discussed how although the builder pattern offers great benefit to the object creator it does add work for the object writer in terms of maintaining the builder. Finally I showed you that all the maintenance and boilerplate can be handled by 1 simple Lombok annotation @Builder. Happy coding!