Design Patterns offers a lot of great approaches to solving recurrent and complex problems on a fancy and understandable way.
Throughout this article, I want to show you how and when you should use the Builder Pattern, so you can build your business logic's complex objects on an efficient and elegant way.
The Builder Pattern falls on the 'Creational Patterns' category. If you look about this Pattern on the web, you'll find several variants of it, depending on the specific problem you might want to solve. Today, I'll show you two common approaches: The Fluent Builder and the Facade Builder.
The Fluent Builder
I think that a good way to explain how a Fluent Builder works is by showing up the StringBuilder
class in action.
StringBuilder sb = new StringBuilder()
sb.append("A ")
.append("big ")
.append("black ")
.append("bug ")
.append("bit ")
.append("a ")
.append("big ")
.append("black ")
.append("dog ")
.append("on ")
.append("his ")
.append("big ")
.append("black ")
.append("nose!")
println sb.toString()
By looking to StringBuilder
's api, we realize that this class provides a fluent interface (chainable mechanism) for building long and complex strings by invoking the append()
method consecutive times. This is achieved by returning the Builder class itself at the very end of the append()
method:
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
This is a very basic example, where the same method is invoked several times in order to build a complex string. Yet, the same mechanism should be used to call all the available Builder's methods, so the object can be seamlessly built.
The classical example
Let's imagine you need to build an instance of the Person
class.
class Person {
String firstName
String lastName
int age
String personalEmail
String country
String city
String address
String houseNbr
String job
String company
String jobEmail
String jobAddress
Person(String firstName, String lastName, int age, String personalEmail, String country, String city, String address, String houseNbr, String job, String company, String jobEmail, String jobAddress) {
this.firstName = firstName
this.lastName = lastName
this.age = age
this.personalEmail = personalEmail
this.country = country
this.city = city
this.address = address
this.houseNbr = houseNbr
this.job = job
this.company = company
this.jobEmail = jobEmail
this.jobAddress = jobAddress
}
}
As you can see, the class itself has so many fields... and making a default constructor that receives all those required fields is just insane.
This approach just doesn't work. Any developer can mistakenly switch any of those fields during constructor invocation.
How can you solve this?! Fluent Builder pattern to the rescue!
Basically, with no surprises, you just have to provide a Person's Builder mechanism that must be used everytime anyone wants to get a Person's instance object.
To achieve this, you can simply implement a PersonBuilder
class that holds all the required steps to build a Person object and returns the object itself at the very end of the process.
I rather have a static inner Builder class, inside the Person's class, to achieve the same result.
Let's do this!
First, we must assure that the Person
class provides a default empty constructor to be used by the Builder
, so it can start building the object. Be aware that this constructor must be declared as private so no one - outside the class's scope - can instantiate a Person's object through it!. Since our Builder class will live inside the Person's class scope, it will be able to access the constructor.
class Person {
(...)
private Person() {} // the empty private constructor to be accessed by the inner Builder class
}
Ok, now that we have the default private constructor, it's time to start building our Person's Builder.
class Person {
(...)
static class Builder {
Person p
Builder() {
p = new Person()
}
}
}
The Builder inner class is declared static so it can be easily accessible outside the Person's class scope (
new Person.Builder()
)The Builder class declares and initializes an empty Person's instance on its constructor that will be built along its builder methods.
The Builder
's class skeleton is ready. Let's add the required steps (builder methods) so we can start building our Person's object:
class Person {
(...)
static class Builder {
Person p
Builder() {
p = new Person()
}
Builder setFirstName(String aFirstName) {
p.firstName = aFirstName
return this
}
Builder setLastName(String aLastName) {
p.lastName = aLastName
return this
}
Builder setAge(int aAge) {
p.age = aAge
return this
}
Builder setPersonalEmail(String aPersonalEmail) {
p.personalEmail = aPersonalEmail
return this
}
Builder setCountry(String aCountry) {
p.country = aCountry
return this
}
Builder setCity(String aCity) {
p.city = aCity
return this
}
Builder setAddress(String aAddress) {
p.address = aAddress
return this
}
Builder setHouseNbr(String aHouseNbr) {
p.houseNbr = aHouseNbr
return this
}
Builder setJob(String aJob) {
p.job = aJob
return this
}
Builder setCompany(String aCompany) {
p.company = aCompany
return this
}
Builder setJobAddress(String aJobAddress) {
p.jobAddress = aJobAddress
return this
}
Builder setJobEmail(String aJobEmail) {
p.address = aJobEmail
return this
}
Person build() {
return p
}
}
}
Notice that in order to provide a fluent interface, we need to return the Builder
itself at the end of each step of the build process, except on the build()
method, where we must return the (fully constructed) Person's object.
Again, the build()
method should only be invoked when the object is completely built.
Using the Person::Builder
Now that our Builder
is ready, let's see how we can use it to build a Person's object:
Person person = new Person.Builder() // the empty Person's object is created
.setFirstName("John")
.setLastName("Doe")
.setAge(32)
.setPersonalEmail("john.doe@email.com")
.setCountry("USA")
.setCity("Chicago")
.setAddress("Some street address")
.setHouseNbr("Some house nbr")
.setJob("Software Developer")
.setCompany("Easy Soft dot com")
.setJobEmail("john.doe@easy.com")
.setJob("some job address")
.build() // returns the fully constructed object
Regarding the first approach, with a default constructor that receives way many arguments, this is undoubtedly a more elegant and efficient approach. By exposing the Builder's api, it becomes easier to understand how to build the object.
However, we can make things even more interesting. On the use case above, we use a single Builder to build the object. But sometimes, your object is so complex, that you need to break down your Builder into multiple 'low level' Builders, that'll work in tandem to provide the required functionality to build the object.
I'm talking about the Facade Builder.
The Facade Builder
The Facade Builder owes its name to the fact of combining (who would tell) the Facade Pattern with the Builder Pattern, to merge multiple fluent interfaces into a single interface, to build very complex objects.
I'm aware that our Person
object is not a so complex object... but I can fairly use it to show you how you can use this Builder Pattern variant to build Person's objects.
By looking into the Person
class fields, let's see how many builders we can take from it.
class Person {
// personal info
String firstName
String lastName
int age
String personalEmail
// address
String country
String city
String address
String houseNbr
// job profile
String job
String company
String jobEmail
String jobAddress
}
At first glance, it seems we can identify 3 distinct builders:
- A base Builder, containing the Person's name, age and personal email
- An Address Builder, responsible for holding the functionality to build the Person's address
- And finally, a Job Builder, responsible for building the Person's job profile.
So, how can we combine all those builder interfaces to build a single Person object?
By following the rules:
- Have base Builder class (the facade interface)
- The base Builder class must instantiate an empty object of the Person class
- All other Builders must extend from the above base Builder
- Those 'child Builders' must receive, on their constructor, a reference of the empty Person object created on the base Builder class
- The base Builder class must provide functionality that allows from switching between the available builders.
The recipe is on the table. Time to start cooking the pattern!
We'll start with the Facade Builder (the required base Builder, remember?)
class Person {
(...)
private Person() {}
static class Builder {
protected Person p
protected Builder() {
p = new Person()
}
Builder setFirstName(String aFirstName) {
p.firstName = aFirstName
return this
}
Builder setLastName(String aLastName) {
p.lastName = aLastName
return this
}
Builder setAge(int aAge) {
p.age = aAge
return this
}
Builder setPersonalEmail(String aPersonalEmail) {
p.personalEmail = aPersonalEmail
return this
}
AddressBuilder livesAt() {
return new AddressBuilder(p)
}
JobBuilder worksAt() {
return new JobBuilder(p)
}
Person build() {
return p
}
}
}
As you can see, we keep the Person's constructor private so we assure that Person objects can only be built through the base Builder class.
Our base Builder class is an inner class of the Person class, so it can access Person's private empty constructor. It also declares the
Person p
field as protected, so it can only be accessed by the 'low level' (child) builders.The base Builder class contains 2 important methods,
livesAt()
andworksAt()
, that allow us to switch between the base Builder class and theAddressBuilder
andJobBuilder
classes during the object building process.
The Facade Builder is set, let's now create the AddressBuilder
and JobBuilder
classes
class AddressBuilder extends Person.Builder {
AddressBuilder(Person person) {
p = person
}
AddressBuilder setCountry(String aCountry) {
p.country = aCountry
return this
}
AddressBuilder setCity(String aCity) {
p.city = aCity
return this
}
AddressBuilder setAddress(String aAddress) {
p.address = aAddress
return this
}
AddressBuilder setHouseNbr(String aHouseNbr) {
p.houseNbr = aHouseNbr
return this
}
}
class JobBuilder extends Person.Builder {
JobBuilder(Person person) {
p = person
}
JobBuilder setJob(String aJob) {
p.job = aJob
return this
}
JobBuilder setCompany(String aCompany) {
p.company = aCompany
return this
}
JobBuilder setJobAddress(String aJobAddress) {
p.jobAddress = aJobAddress
return this
}
JobBuilder setJobEmail(String aJobEmail) {
p.jobEmail = aJobEmail
return this
}
}
The 'low level' builders extends from the base class
They also receive a reference of the empty Person object - from the base Builder class - on their constructors, so they can keep building the object from the moment they're invoked.
Things are looking good... it seems our Person's facade Builder is completely set up. Let's see it in action:
Person person =
new Person.Builder()
.setFirstName("John")
.setLastName("Doe")
.setAge(30)
.setPersonalEmail("john.doe@email.com")
.livesAt()
.setCountry("USA")
.setCity("Chicago")
.setAddress("10th Avenue")
.setHouseNbr("5400 N. Lakewood Avenue")
.worksAt()
.setJob("Software Developer")
.setCompany("Easy Soft dot com")
.setJobEmail("john.doe@easy.com")
.setJobAddress("12th Avenue")
.build()
Wow! This code looks pretty cooler than the last one.
By combining the available builders, we provided a facade interface that allows building Person's objects on a simpler and intuitive way. Also, by wrapping up the building steps into multiple categorized builders, we can provide a more organized api, giving some kind of guidance during the object's construction.
One thing I did not do was to add a validate()
step within the build()
method that assures that the object has all the required fields fulfilled before return it. This is a crucial step, yet (in my opinion) is one of the great caveats of these approaches.
Conclusion
Stop making complex objects with messy constructors and start using the Builder Pattern approach :)
Now that you know how to use the Builder Pattern, I hope that from now on, everytime you need to build objects, you can think on how this Pattern can help you on solving your problem.
Do you have any other useful info about this Pattern to share with us? Don't be shy, share it below :)
Stay tunned!
Top comments (0)