Skip to content

Builder

Last Updated on 14/11/2020

Builder Design Pattern Logo

Builder is a creational design pattern which aims on decoupling the construction logic of a complex object from its representation.

Introduction

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.

Builder Structure

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:

Simple Builder UML Diagram

Some important considerations about the diagram:

  • Client can be any class that needs to instantiate an object from Customer class. In our example, it’s going to be a class containing different ways to instantiate the Customer class, in order to discuss the pattern utilization;
  • All methods except build from CustomerBuilder interface, are declared using fluent interface, an implementation which provides more readable code by writing code in such a language that a human would use on general communication. Methods designed over fluent interface always return the object instance from where they were invoked. Adopting fluent interfaces is optional, check the code snippets for examples;
  • The build method returns the instance from which the builder was designed.

Implementation

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;
	}
}

Discussion

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();

Conclusion

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.

2 thoughts on “Builder”

Leave a Reply

Your email address will not be published. Required fields are marked *