How to implement a simple Builder Pattern in java
In design patterns, the builder pattern is one of those patterns that fascinates me. It is a creational Design pattern that simplifies how we deal with complex objects and optional parameters. According to wikipedia, the intent of the builder design pattern is to separate the construction of a complex object from its representation.
On Grepper, Atul Verma gives a few points on when to use the builder pattern:
- When the process involved in creating an object is extremely complex, with lots of mandatory and optional parameters
- When an increase in the number of constructor parameters leads to a large list of constructors
- When client expects different representations for the object that’s constructed
A spoiler alert, you may find that my implementation of the builder pattern differs from your expectations, my primary goal was to implement how to use Builder()
class and build()
method like in the okhttp3
package’s Request
class.
Request request = new Request.Builder()
.url(myUrl)
.addHeader("Content-Type", "application/json")
.post(body)
.build();
In this article, we create a Car
class that we use to create new Cars
as we are going to explore:
- Working with
no args constructor
setting properties usingsetters.
- Working with an
all args constructor
passing parameters to the constructor. - Lastly, we work with the
builder
pattern.
The intent of the builder design pattern is to separate the construction of a complex object from its representation
Using 'No args constructor'
We have created two classes, Car
class and Main
class. Inside the Car
class we have all the properties that make up a car and it’s setters as well as the toString()
method which helps us to print out the properties to the screen.
In the above code snippet, inside the Main
class we create an instance of the Car
class and we leverage the default no args constructor
(created by the compiler in case no constructor is present in a class) to create the car1
instance and we set all the car properties on the car1
instance.
2. Using 'all args constructor'
In this section, we add the required changes inside the existing implementation of the 'No args constructor'
logic and finally we have an implementation that supports both working with setters and using the constructor to create instances of a Car
. New changes are found at the end of the class.
In the above snippets, we modify the Car
class to add the all args constructor
and no args constructor
. We then create the car2
instance from the Car
class and directly pass it’s properties as parameters to the constructor.
3. Builder pattern
In this section, we go ahead and add our builder code to the existing code as we want to maintain the old implementation of constructors functional. The builder pattern implementation presented uses a single class but this can be adjusted to deal with multiple complex classes.
In the above code snippets, In the Car.java
class on line 62
we see the new code that implements the Builder
pattern.
private Car(Builder builder){
this.make = builder.make;
this.name = builder.name;
......
}
Looking at this.make= builder.make;
, it assigns the value of make
from Builder
class to the make
property of the Car
class, copying the value from the builder to the actual object being constructed. This happens for the rest of the properties.
At line 70 of the Car.java
class, there is an inner Builder
class inside the Car.java
class. The Builder
class has the public and static
modifier which makes it accessible and instantiable outside of the Car
class without creating an instance of the Car
class.
Car.Builder newBuilder = new Car.Builder();
Inside the Builder
class, we have a couple of encapsulated properties that correspond to the properties of the Car
class and we methods that are accessible outside of the Builder
class each returning the Builder
class itself.
public static class Builder{
private String make;
.....
private List<String> countriesPresent = new ArrayList<>();
public Builder make(String make){
this.make = make;
return this;
}
.....
public Builder countriesPresent(List<String> countriesPresent){
this.countriesPresent = countriesPresent;
return this;
}
public Car build(){
return new Car(this);
}
}
Lastly in the Builder
class we have a method build
that returns the Car
class.
public Car build(){ return new Car(this); }
The build
method is a public method returning a Car
type. The method is responsible for creating and returning an instance of the Car
class. return new Car(this);
this instantiates a new Car
object, passes the current instance of builder ( this
) as an argument to the Car
constructor. The purpose of returning the Car object is to finalize the construction of the Car
object using the properties set in the builder and return the fully constructed Car
instance.
This build
method is called at the end of the chain of the setter methods in the builder pattern. The builder accumulates the values set by various setter methods, and when the client code is finished configuring the object, it calls the build
method to create the actual instance of the desired object (in this case, a Car
). The build
method encapsulates the logic needed to construct the object, often by invoking a constructor with the accumulated values from the builder.
Now that we have the Builder
class fully implemented, we then can use it in the Main
class and set only the properties of choice using the respective Builder
methods.
Car car3 = new Car.Builder()
.make("Ford")
.name("Mastunga")
.color("Red")
.model(2023)
.countriesPresent(List.of("Mexico", "Brazil"))
.build();
Here is the output when this piece of code is run.
As we conclude, remove the countriesPresent method
that takes in a list of countries and instead create a method that will allow us to add countries on a one by one basis and not a list.
Builder Code Snippet
Final code snippet
With this, I hope you have learnt a thing or two about the Builder pattern and can use it in your day to day task.
Till next time, have a blessed day.
References