Builder is a creational design pattern which aims on decoupling the construction logic of a complex object from its representation.
Sometimes an object construction can be complex, because of parameter validations, search of informations on files or database, or even for numerous parameters the object’s constructor is waiting to receive. If you mix up the logic creational logic along the class’s behaviour logic, you may lose the class’s cohesion and can make it difficult to reuse.
Builder design pattern comes to solve this problem, putting both creational and behavioural logic on it right places, by providing enough encapsulation for object’s construction, in such a way you can even develop any number of different implementations for a single builder.
We are going to create a Customer class, add some validations on its parameters and make one of them mandatory to demonstrate the pattern.
Below is the UML representation:
Some important considerations about the diagram:
Below are the Java classes which implement the pattern. Some javadoc, comments, getters, setters, constructors and toString
overwritten were omitted for readability purposes. Please refer to the GitHub repository for the full version of this code.
Customer: the class we need to instantiate in order to use it in our client classes:
package com.bgasparotto.designpatterns.builder; import java.util.HashSet; import java.util.Set; public class Customer { private long id; private String name; private String city; private Set<Phone> phones; public Customer(long id, String name, String city, Set<Phone> phones) { this.id = id; this.name = name; this.city = city; this.phones = phones; } public Customer(String name, String city, Set<Phone> phones) { this(0, name, city, phones); } public Customer(String name) { this(0, name, null, new HashSet<Phone>()); } // Getters and setters. }
Phone: just a Customer’s dependency, something we face in our daily coding:
package com.bgasparotto.designpatterns.builder; public class Phone { private long id; private String number; public Phone(long id, String number) { this.id = id; this.number = number; } public Phone(String number) { this(0, number); } // Getters and setters. }
CustomerBuilder and CustomerBuilderImpl: the pattern contract and implementation, respectively:
package com.bgasparotto.designpatterns.builder; public interface CustomerBuilder { CustomerBuilder fromCity(String cityName); CustomerBuilder hasPhone(String phoneNumber); Customer build(); }
package com.bgasparotto.designpatterns.builder; import java.util.Set; public class CustomerBuilderImpl implements CustomerBuilder { private Customer customer; public CustomerBuilderImpl(String name) { this.customer = new Customer(name); } @Override public CustomerBuilder fromCity(String cityName) { customer.setCity(cityName); return this; } @Override public CustomerBuilder hasPhone(String phoneNumber) { Phone phone = new Phone(phoneNumber); Set<Phone> phones = customer.getPhones(); phones.add(phone); return this; } @Override public Customer build() { return customer; } }
Say we need to instantiate a Customer object and all we have is its name, which happen to be the only mandatory parameter. In this case, independently of using the pattern, both solutions are clean but with no pattern, it is even shorter:
// Instantiate the Customer passing the name directly customer = new Customer("Bruno"); // Instantiate the Customer passing the name to the Builder CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.build();
Now let’s say we need to pass the cityName besides the customer’s name. Both solutions still clean, but without the pattern we have to explicitly call the exposed method setCity(String), coupling our client code to Customer’s implementation details:
// Instantiate a Customer and set its city. customer = new Customer("Bruno"); customer.setCity("Sao Paulo"); // Instantiate a Customer passing the city to the fluent builder interface CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.fromCity("Sao Paulo").build();
Next, apart from the previous arguments, we need to pass a phone number in order to create our instance, then we can observe that as the argument list grows, as the client’s code complexity without the pattern does:
// Passing the city and phone without the pattern. customer = new Customer("Bruno"); customer.setCity("Sao Paulo"); Phone mobile = new Phone("999999999"); Set<Phone> phones = customer.getPhones(); phones.add(mobile); // Passing the city and phone with the pattern. CustomerBuilder builder = new CustomerBuilderImpl("Bruno"); customer = builder.fromCity("Sao Paulo").hasPhone("999999999").build();
Besides the extra lines of code when we needed to pass more arguments, it’s important to note that in our no-pattern version, we had to couple our client’s code to Customer‘s setters in order to configure its attributes. It might become a problem in the future if we need, in example, to get rid of the setters and rely only on constructor’s arguments to make the Customer class immutable, or change the hierarchy between Customer and Phone classes. At this point, without the pattern we might have to modify every single class which creates a customer, whilst we could focus our maintenance on just the Builder classes.
This guide will show you how to create a Python function decorator with a few…
This guide will show you how to fix the error Got permission denied while trying…
This guide will show you how to create a Python virtual environment on Intellij IDEA…
This tutorial will quickly show you how to to find and kill processes on Linux,…
This guide shows a possible solution for Python error Relocation R_X86_64_PC32 against symbol can not…
I condensed below a cheat sheet of Kubernetes useful commands. I will keep updating this…
View Comments
Congrats this was very usefull.
Thanks Michel!