Builder Design Pattern

What is Builder Pattern?

The Builder is a creational design pattern that allows us to follow a step-by-step process to construct a complex object. Builder design pattern separates the construction of the complex object from its representation by which the same process can create different representations (types) of the complex object.

Problem Statement

Let’s make some Pizzas!! We’ll understand the builder design pattern with an example of making pizzas.

Making a pizza will consist of a recipe (series of steps). We might start with the dough, and then we’ll go to the pizza base, then the toppings followed by the sauce, and finally the baking part comes into play.

Solution Approach 1

We can treat Pizza as a class with many fields like dough, pizza base, toppings, etc. Then we create a pizza using the constructor of the Pizza class.

Code

//Pizza class with constructor
public class Pizza {
    
    //...
    
    private String dough;
    private String base;
    private String toppings;
    private String sauce;
    private String bake;
    private String cheese;
    public Pizza (String dough, String base, String toppings, 
                        String sauce, String bake, String cheese) 
    {
        this.dough = dough;
        this.base = base;
        this.toppings =  toppings;
        this.sauce = sauce;
        this.bake = bake;
        this.cheese = cheese;
    }
    
    //...
}

Well, that solves the problem, right? But the next consumer wants to have some extra toppings along with mozzarella cheese on it while another consumer wants it without any toppings. Now, we are forced to change our Pizza class and add a set of overloaded constructors, like this:

//set of overloaded constructors which are added in Pizza class

public Pizza(String dough, String base, String toppings,String sauce, String bake, String cheese)
{
    //...
}
    
public Pizza(String dough, String base, String sauce, String bake, String cheese)
{
    //pizza without toppings...  
}
public Pizza(String dough, String base, String toppings, String sauce, String bake)
{
    //pizza without cheese...
}

Drawbacks

  • Technically, the set of overloaded constructors will give an error because the compiler will not be able to distinguish between the second and third constructors due to their same signature. Therefore, this is a crucial drawback: if the class fields have similar data types, we will have difficulty using constructor overloading for instantiation.
  • As the number of combinations of parameters increases, the number of constructors will increase, suggesting that this approach is inefficient for complex and heavy classes (in terms of the class's number of parameters).

Takeaway: This particular approach is often termed a telescopic constructor pattern, which is, in fact, an anti-pattern. So in place of using the telescopic constructor pattern, we should try a better approach.

Solution Approach 2

To overcome the drawbacks in the telescopic constructor pattern, we can write setter methods for each field in the class and initialize them. 

Code

//... 
public void setdough (String dough) {
    this.dough = dough;
}
public void setbase (String base) {
    this.base = base;
 }
public void setsauce (String sauce) {
    this.sauce = sauce;
}
//and so on....
//...

Drawbacks

  • For this approach to work, consumers have to call setter methods from the consumer part of the code with appropriate parameters in the correct order to make a pizza. Imagine, if a consumer calls setsauce() before setdough(), then he won’t end up with a pizza! This can happen quite often because the consumer (client) might not be aware of the recipe for pizza.
  • Moreover, if a consumer shows up and says “I want a large size Mexican pizza”. Then the consumer part of the code has to call all the setters with new values which will be tedious.

Takeaway

  • The order in which the steps are performed is also our concern so, we should not give this responsibility to the consumer/client code.
  • We should try to remove the responsibility of calling the setter methods from the consumer code because we may have a number of varieties of pizzas that require each step to work differently.

The Builder pattern helps in solving such issues while creating a complex product.

Efficient Solution Approach

We know that all pizzas have to follow the same procedure (recipe) but the implementation of the steps might be different. For instance, an Italian pizza has different toppings, cheese from that of a Mexican pizza but, the steps performed in making both pizzas are the same. Therefore, we will first try to separate the algorithm of making the pizza that is, the recipe part from how a pizza is created and represented or, how the steps of the recipe are implemented and kept as the final product. 

So, we will hire a HeadChef who knows the recipe and we will also hire some talented Cooks who are specialized in making a certain type of pizza. For instance, ItalianCook knows how to make an Italian pizza whereas MexicanCook knows how to make a Mexican pizza. The role of HeadChef is to define the recipe to a Cook and then the other Cook will follow the recipe and finally give back the pizza to HeadChef.

Notice that each Cook is supposed to follow the company's policy in making a pizza that is, they are only allowed to use a common interface of steps but they have to implement the steps as per the requirement of their pizza. For instance, MexicanCook can not add a new component in the Pizza class by himself. To solve these issues, we will introduce an interface named Cook that declares all the possible steps that can be involved in pizza making.

In terms of classes and interfaces, the HeadChef will be a class that defines the steps in the correct order. Cook is an interface that consists of the methods to set the fields required in the Pizza product (e.g. dough, sauce, etc). All the sub-classes of Cook (MexicanCook and ItalianCook) implement the methods provided in the Cook. Finally, the complete product (pizza) is returned by one of the concrete subclasses of Cook.

Takeaway

  • We have abstracted two different responsibilities:
    — Order of construction steps (recipe).
    — Ingredients/style of construction in each step.
  • We will be using the same construction process (which is provided by HeadChef) to create different representations of our object (the Pizza).
  • Also, the Cook interface hides the details of the construction process from the consumer. Therefore, now the consumer needs to associate a specific Cook with the HeadChef (he or she can do so by ordering a specific pizza) and he or she will get their order ready.
  • Adding new types of Cooks is simple because we only have to make a new subclass of Cook and implement it. Whereas, in the previous approach, we had to change the existing consumer code. 

The final approach that we just discussed is the Builder pattern.

*NOTE: We can make more classes like HeadChef say PrimeChef, which might have a different construction process as per the need of the company, or we can define a new construction process in the HeadChef class itself. Then, we can use PrimeChef class with existing Cooks to get new desirable pizzas! In our example, we’ve considered only a single construction process, but there can be many construction processes with minute differences. For instance, maybe a super-fast variant of pizza has some other steps as compared to a general pizza.*

Components and Structure

Components and Structure of Builder Design Pattern

  1. Builder (Cook) An interface that declares steps of product construction common to all of its subclasses.
  2. Concrete Builders (ItalianCook, MexicanCook)Implement methods of Builder class differently as per the demand of consumers. Different concrete builders provide different implementations so that, we can get different representations of the complex product.
  3. Director (HeadChef)Defines the appropriate order in which all the construction steps have to be invoked. Uses the Builder interface to create an instance of the complex product.
  4. Product (Pizza)The final objects are returned by the concrete builders. Again, we emphasize that all the products need not belong to the same base class, although in this example we have taken all the products to belong to the same class Pizza but the products can belong to different classes. In such a case, the consumer code directly calls the method of Concrete builders which return the final product. (We’ll look at a small example later)
  5. Client (Consumer)This part of the code associates one of the concrete builders with the director. Then, constructs the product via the director class.

Implementation

We’ve already explored the problem and various possible solutions, among which the first two approaches were inefficient and the last approach is the builder pattern which overcomes the drawbacks of the rest of the approaches up to great extent.

Builder Design Pattern Example

Implementation Approach (Java)

Firstly, we create the product class — Pizza, which consists of various fields and setter methods of those fields (e.g, setdough(), setbase(), etc). Notice that these parts can be objects of some other class also.

public static class Pizza {
    private String dough;
    private String base;
    private String toppings;
    private String sauce;
    private String bake;
    private String cheese;
        
    public void setdough (String dough) {
        this.dough = dough;
    }
    public void setbase (String base) {
        this.base = base;
    }
    public void settoppings (String toppings) {
        this.toppings = toppings;
    }
    public void setsauce (String sauce) {
        this.sauce = sauce;
    }
    public void setbake (String bake) {
        this.bake = bake;
    }
    public void setcheese (String cheese) {
        this.cheese = cheese;
    }
    public void ShowPizza () {
        System.out.println(dough+", "+base+", "+toppings+",   "+sauce+", "+bake+", "+cheese);
    }
}

Then, we create the Builder class — Cook, which declares an interface consisting of all the steps of construction involved. 

public static interface Cook {
    public void builddough();
    public void buildbase();
    public void buildtoppings();
    public void buildsauce();
    public void buildbake();
    public void buildcheese();
    
    public Pizza GetPizza();    
}

A major point to focus on is, whether this interface will declare a method for returning the instance of the Pizza class or its concrete subclass will do it on its own? The answer depends on the final representations of the complex product. Ideally, all the possible final representations (that is, all types of pizzas) will belong to the same Pizza class. But if final products differ a lot then there is no point in representing them with the same base class/interface. 

Therefore, if each representation of the final product belongs to the same base class then we can declare the method for retrieving the final product in the Builder interface otherwise we have to do it individually in the concrete builder classes.

Now, we subclass the Cook interface with different Cooks (e.g, MexicanCook and ItalianCook). These concrete builders provide implementations of the construction steps declared in Cook and will return the final product after applying all the steps.

ItalianCook Class

public static class ItalianCook implements Cook {
    private Pizza pizza;
    public ItalianCook() {
        this.pizza = new Pizza(); 
    }
    @Override
    public void builddough() {
        pizza.setdough("Italian Dough");
    }
    @Override
    public void buildbase() {
        pizza.setbase("Italian Base");
    }
    @Override
    public void buildtoppings() {
        pizza.settoppings("Italian Toppings");
    }
    @Override
    public void buildsauce() {
        pizza.setsauce("Italian Sauce");
    }
    @Override
    public void buildbake() {
        pizza.setbake("Bake");
    }
    @Override
    public void buildcheese() {
        pizza.setcheese("Cheese");
    }
        
    @Override
    public Pizza GetPizza() {
        Pizza final_pizza = this.pizza;
        this.pizza = new Pizza();
        return final_pizza;
    }
}

MexicanCook Class

public static class MexicanCook implements Cook {
    private Pizza pizza;
    public MexicanCook() {
        this.pizza = new Pizza(); 
    }
    @Override
    public void builddough() {
        pizza.setdough("Mexican Dough");
    }
    @Override
    public void buildbase() {
        pizza.setbase("Mexican Base");
    }
    @Override
    public void buildtoppings() {
        pizza.settoppings("Mexican Toppings");
    }
    @Override
    public void buildsauce() {
        pizza.setsauce("Mexican Sauce");
    }
    @Override
    public void buildbake() {
        pizza.setbake("Bake");
    }
    @Override
    public void buildcheese() {
        pizza.setcheese("Cheese");
    }
        
    @Override
    public Pizza GetPizza() {
        Pizza final_pizza = this.pizza;
        this.pizza = new Pizza();
        return final_pizza;
    }
}

NOTE: In the GetPizza() we’ve set the pizza field as a fresh Pizza so that whenever a pizza is completed a new pizza can be made in the future using the same object of Cook class (technically, the same object of a concrete subclass of Cook).

Now, we declare the director-class — HeadChef that is responsible for defining the order in which steps are executed. There can be more than one director or a single director can itself have several construction processes. Here we’ve considered a single algorithm and a single Director.

public static class HeadChef {
    private Cook cook;
    public HeadChef (Cook cook) {
        this.cook = cook;
    }
    public void MakePizza() {
        cook.builddough();
        cook.buildbase();
        cook.buildtoppings();
        cook.buildsauce();
        cook.buildbake();
        cook.buildcheese();
    }
}

Finally, we create the Consumer code which will create a concrete builder object and passes it to the director (HeadChef). Then, the director will apply the construction process with this builder object and finally, the complex product is retrieved either via the director or through the builder itself. 
Retrieving the final product from the builder is also possible because the Client usually configures the director with the proper concrete builder, that is, the client knows which concrete builder will give him the product.

public static void main (String[] args){
    Cook cook = new ItalianCook();
    HeadChef headchef = new HeadChef (cook);
       
    headchef.MakePizza();
    Pizza pizza = cook.GetPizza();
    pizza.ShowPizza();
    cook = new MexicanCook();
    headchef = new HeadChef (cook);
    headchef.MakePizza();
    pizza = cook.GetPizza();
    pizza.ShowPizza();
}

Implementation code C++

Product class Pizza

#include<bits/stdc++.h>
using namespace std;
class Pizza {
    private:
    string dough;
    string base;
    string toppings;
    string sauce;
    string bake;
    string cheese;
public:
    void setdough (string dough) {
        this->dough = dough;
    }
    void setbase (string base) {
        this->base = base;
    }
    void settoppings (string toppings) {
        this->toppings = toppings;
    }
    void setsauce (string sauce) {
        this->sauce = sauce;
    }
    void setbake (string bake) {
        this->bake = bake;
    }
    void setcheese (string cheese) {
        this->cheese = cheese;
    }
    void ShowPizza () {
        cout<<dough<<", "<<base<<", "<<toppings<<", 
                     "<<sauce<<", "<<bake<<", "<<cheese<<endl;
    }
};

Builder interface Cook: the interface to declare steps of construction that is, methods that construct various parts of the product.

class Cook {
    public:
    virtual void builddough(){};
    virtual void buildbase(){};
    virtual void buildtoppings(){};
    virtual void buildsauce(){};
    virtual void buildbake(){};
    virtual void buildcheese(){};
    virtual Pizza* GetPizza (){};
};

Concrete Builder: ItalianCook

class ItalianCook : public Cook {
    //pizza is the final product this class will work on
    private:
    Pizza* pizza;
    
    public:
    ItalianCook() {
        this->pizza = new Pizza();
    }
    
    void builddough() override {
        pizza->setdough("Italian Dough");
    }
    void buildbase() override {
        pizza->setbase("Italian Base");
    }
    void buildtoppings() override {
        pizza->settoppings("Italian Toppings");
    }
    void buildsauce() override {
        pizza->setsauce("Italian Sauce");
    }
    void buildbake() override {
        pizza->setbake("Bake");
    }
    void buildcheese() override {
        pizza->setcheese("Cheese");
    }
    Pizza* GetPizza() override {
        Pizza* final_pizza = this->pizza;
        this->pizza = new Pizza();
        return final_pizza;
    }
};

Concrete Builder: MexicanCook

class MexicanCook : public Cook {
    //pizza is the final product this class will work on
    private:
    Pizza* pizza;
    public:
   
    MexicanCook() {
        this->pizza = new Pizza();
    }
    //implementing the steps
    void builddough() override {
        pizza->setdough("Mexican Dough");
    }
    void buildbase() override {
        pizza->setbase("Mexican Base");
    }
    void buildtoppings() override {
        pizza->settoppings("Mexican Toppings");
    }
    void buildsauce() override {
        pizza->setsauce("Mexican Sauce");
    }
    void buildbake() override {
        pizza->setbake("Bake");
    }
    void buildcheese() override {
        pizza->setcheese("Cheese");
    }
    Pizza* GetPizza() override {
        Pizza* final_pizza = this->pizza;
        this->pizza = new Pizza();
        return final_pizza;        
    }
};

Director Class HeadChef: responsible for defining the order in which steps are executed. There can be more than one director or a single director can itself have several construction processes. Here we’ve considered a single algorithm and a single Director.

class HeadChef {
    private:
    Cook* cook;
    //cook is the concrete builder with which director works
    public:
    HeadChef (Cook* cook) {
        this->cook = cook;
    }
    //using a HeadCHef without a cook is illogical therfore
    //we define a parameterized constructor
    void MakePizza() {
        cook->builddough();
        cook->buildbase();
        cook->buildtoppings();
        cook->buildsauce();
        cook->buildbake();
        cook->buildcheese();
    }
};

In this code, the main() acts as the Consumer code.

int main() {
    Cook* supercook = new ItalianCook();
    
    HeadChef* headchef = new HeadChef (cook);
    
    headchef->MakePizza();
    
    Pizza* pizza = cook->GetPizza();
    pizza->ShowPizza();
    return 0;
}

When to apply Builder Pattern

We can use the builder pattern when

  • Using a telescopic constructor is not efficient.
  • The same construction process is to be used to create different representations of the complex product.
  • The process of creation of the product should be independent of the parts that make up the object and how they’re assembled.

    In our example of Pizza making, the process of creation is defined by the HeadChef class whereas the parts that make up the object are implemented in the concrete builders that is, in ItalianCook and MexicanCook. It can be seen that the Builder class (Cook) hides the internal representation of the Pizza product that is, it hides the classes that define various parts of pizza and how these parts are assembled to complete the final product. Therefore, the process defined by HeadChef class is independent of the parts of the pizza product.
  • Adding new representations and removing existing representations of the complex product is frequent and needs to be flexible.

Consequences

  • It makes modifying representations easy and flexible. As we’ve already seen, the builder object interacts with the director via an abstract interface. Also, each builder implements that interface in a particular manner to provide a specific representation of the product. Because, the product is constructed through an interface, therefore, to add new representations, we’ve to add a new kind of concrete builder that implements the interface in a specific manner.
  • It reduces the responsibility on the consumer (client) code. The consumer only needs to associate the correct builder class with the appropriate director-class (remember that there may exist more than one director).
  • It allows us to use the same construction process to build different representations of the complex product.
  • Additionally, the builder pattern gives finer control over the construction process. As compared to the factory method or abstract factory method who creates the objects in a single function call (that is, in one go!), the Builder pattern follows the step-by-step approach as directed by the director. The Builder interface depicts that, the final product is created by creating its various parts in a step-by-step manner. This gives us finer control over the construction process.
  • It makes the application flexible at the cost of increases in the complexity and number of classes in the code.

Used in

Some popular uses of the builder pattern can be found

Conclusion

We saw some possible but inefficient approaches such as construction involving telescopic constructor and construction using setter methods via consumer code. Then, we saw the builder pattern and explored how this pattern solves the problems involved in previous approaches. For e.g, highly responsible consumer code, usage of the same process in the correct order, etc. Finally, we saw some of its applicability and its effect on the entire creational process.

Enjoy learning, Enjoy OOPS!

More from EnjoyAlgorithms

Our weekly newsletter

Subscribe to get free weekly content on data structure and algorithms, machine learning, system design, oops design and mathematics.

Follow us on:

LinkedinMedium

© 2020 EnjoyAlgorithms Inc.

All rights reserved.