Builder design pattern is yet another creational pattern which is very useful when you have lots of configurations to perform on the object. The object may have too many attributes to set in constructor. This tutorial discusses about Builder design pattern in great detail with Java language.

What is Builder Pattern?

This is one of the most popular design patterns for creating objects. It’s widely used in frameworks and libraries to create objects that require a lot of parameters. Usually initializing those objects with all parameters might be cumbersome because sometimes they are not needed but other times they might be needed. So, builder pattern provides a way to initialize only required parameters with other parameters getting the default values. This pattern also ensures that the object parameters are immutable once created. If we were to create constructors for this kind of objects, we might end up creating lots of overloaded constructors with few parameters. These are called telescoping constructors which might pollute our code. This pattern solves that problem. It’s used quite a lot in standard Java library. The most common ones are StringBuilder, DocumentBuilder, etc.

How to implement Builder Design Pattern?

Let’s say, we are working for a burger shop and we want to be able to build burger based on some ingredients. These ingredients might vary widely depending on the customer. Some customers might prefer to have vegetables like tomatoes, lettuce, onion, etc. whereas other customers might prefer non-vegetarian ingredients. Some customers might even omit few of the vegetables. Let’s see how we might start creating these burgers. For this demo, I am assuming only three ingredients available: onion, tomatoes, lettuce, but there may be many more. Also, for simplicity, I have represented the ingredients as boolean, but they may have their own class with weight or other measurements.

 1public class Burger {
 2    private boolean onion;
 3    private boolean tomatoes;
 4    private boolean lettuce;
 5
 6    public Burger(boolean onion, boolean tomatoes, boolean lettuce) {
 7        this.onion = onion;
 8        this.tomatoes = tomatoes;
 9        this.lettuce = lettuce;
10    }
11
12    public Burger(boolean onion, boolean tomatoes) {
13        this(onion, tomatoes, false);
14    }
15
16    public Burger(boolean tomatoes) {
17        this(false, tomatoes, false);
18    }
19}

In this case, we have assumed only three possible ingredients and had to add two extra overloaded constructors. On top of that, we also have to have fat constructors which might be initializing lots of parameters which is unreadable.

In order to convert this to a Builder pattern, we have to extract the Burger making into a separate class BurgerBuilder. In order to extract to a builder pattern, we have to follow below steps.

  1. Make constructor for the Product class private so that there is no way to directly invoke it.
  2. Create static inner class Builder in the Product class with single parameter of Product class defined with default values.
  3. Define methods for each parameter of the Product class which returns the Builder itself while setting the parameter for the Product class.
  4. At the end define build() method in the Builder class which actually returns the Product itself.

Following these steps, we will have a code similar to below.

 1package com.github.piyushpatel2005.builder.simple.burger;
 2
 3public class Burger {
 4
 5    private String breadType;
 6    private boolean onion;
 7    private boolean tomatoes;
 8    private boolean lettuce;
 9    private boolean cheese;
10
11    private Burger(String breadType, boolean onion, boolean tomatoes, boolean lettuce, boolean cheese) {
12        this.breadType = breadType;
13        this.onion = onion;
14        this.tomatoes = tomatoes;
15        this.lettuce = lettuce;
16        this.cheese = cheese;
17    }
18
19    public static class Builder {
20        private Burger burger = new Burger("", false, false, false, false);
21
22        public Builder withBread (String breadType) {
23            burger.breadType = breadType;
24            return this;
25        }
26
27        public Builder withTomatoes() {
28            burger.tomatoes = true;
29            return this;
30        }
31
32        public Builder withOnion() {
33            burger.onion = true;
34            return this;
35        }
36
37        public Builder withLettuce() {
38            burger.lettuce = true;
39            return this;
40        }
41
42        public Builder withCheese() {
43            burger.cheese = true;
44            return this;
45        }
46
47        public Burger build() {
48            return burger;
49        }
50
51    }
52
53    @Override
54    public String toString() {
55        return "Burger{" +
56                "breadType='" + breadType + '\'' +
57                ", onion=" + onion +
58                ", tomatoes=" + tomatoes +
59                ", lettuce=" + lettuce +
60                ", cheese=" + cheese +
61                '}';
62    }
63}

Now, the client can easily create objects with required ingredients and it will look lot more cleaner.

 1public class Main {
 2    public static void main(String[] args) {
 3        System.out.println("Create Burger with tomatoes only");
 4        Burger burger = new Burger.Builder()
 5                .withBread("Wheat")
 6                .withTomatoes()
 7                .build();
 8        System.out.println(burger);
 9
10        System.out.println("Create burger with tomatoes, lettuce and cheese");
11        burger = new Burger.Builder()
12                .withBread("Wheat")
13                .withTomatoes()
14                .withLettuce()
15                .withCheese()
16                .build();
17        System.out.println(burger);
18    }
19}

Builder Design Pattern with Director

Although above pattern works, we might segregate construction of object out of client. In above code, the client code is actually building the Product which may not be convenient. We can extract those construction logic into Director class. The director class simply provides building steps and the bulider provides the actual implementation for those steps. Sometimes, this might be important to make sure we have prerequisite set before setting the next parameter. This class completely hides the details of product construction from the client code. The client simply needs to assign specific type of builder with a director. In this case, we also have more than one type of concrete builders. We will have Builder defining the common interface for creating different types of products with concrete Builders actually implementing the methods from Builder interface.

The client code can pass the actual concrete builder using one of the parameters in the constructor or by using configuration file.

Advantages:

  • This pattern handles complex object creation with multiple parameters or configurations.
  • The builder actually controls the object construction and doesn’t expose the object directly promoting encapsulation.
  • The code for object construction with its properties reside in the same class if implemented as a static inner class.
  • This pattern can also be implemented easily for legacy code.

Disadvantages:

  • Static inner classes add to the complexity of implementation.
  • Having separate builder classes along with the main product classes increase the overall code complexity.
  • Builder pattern for objects with very few parameters might be an overkill.

Comparison with Abstract Factory Pattern:

Abstract Factory PatternBuilder Pattern
focuses on building families of related objectsfocuses on creating object step by step
it returns the product object immediatelyit adds construction steps one by one and finally returns object.
class usually includes less number of params.class includes lots of parameters to pass into constructor.

Summary

  • Builder pattern provides nice construction method to avoid telescoping constructors.
  • It avoids having to write multiple unnecesary constructors to handle large number of optional parameters.
  • With director, we can control the sequence of construction of objects.
  • It abstracts away object creation and follows single responsiblity principle.