Builder Design Pattern in Java

Builder Design Pattern | Here we will discuss what is the builder design pattern in Java? When to use builder design pattern? We will also see the simple example of the builder design pattern in Java. What are the advantages and disadvantages of the builder design pattern?

The Builder design pattern is a creational design pattern used to assemble complex objects. It says to create complex objects step by step by combining multiple independent objects by defining a process, so that process can be used to construct multiple complex objects of the same category. With the builder design pattern, the same object construction process can be used to create different objects of the same category. 

GOF says “Separate the construction of a complex object from its representation so that the same construction process can create different representations” i.e. Builder Design pattern.

In our real-life example:- If we define a process to construct the house by combining the multiple individual tasks like roof, structure, basement, interior, and e.t.c. then that process also can be used to construct the different types of houses like wooden houses, ICE houses (Igloo), Concrete houses and e.t.c. Similarly, If we define a process to construct the meal by combining multiple individual objects then that process can be used to construct veg meal, non-veg meal, kid’s meal, and e.t.c.

Participants of Builder Design Pattern

There are 4 participants of builder design pattern, 

  1. Product:– The complex object to be construct (like house).
  2. Builder:- These are the Interfaces or abstract classes that contains methods to construct various individual objects (like HousePlan) involved in assembling a Product object. It also has a method for retrieving the project object (like getProduct()). 
  3. ConcreteBuilder:- They implements/extends the Builder interfaces/abstract class and contains logics to create various simple independent objects that are required to create product (like WoodenHouseBuilder, IceHouseBuilder, ConcreteHouseBuilder, and e.t.c.) Different Builders create and assemble Product objects differently.
  4. Director/Delegator:- It is responsible for constructing a Product. It uses one or another concrete builder and defines the process to create the product (like Civil Engineer).

Note:- Each builder is independent of others. This improves the modularity and makes the building of other builders easy. Because each builder builds the final product step by step, we will have more control over the final product.

When to Use Builder Design Pattern

Use the Builder design pattern when

  • The creation algorithm of a complex object is independent from the parts that actually compose the object.
  • The system needs to allow different representation for the objects that are being built.

Usecases of Builder Design Pattern

There are various uses of the Builder design pattern. For one, if we’d like the construction process to remain the same but we’d like to create a different type of Product, we can create a new Concrete Builder and pass this to the same Director to use a different construction process.

In Hibernate the way we use configuration objects to create SessionFactory is by specifying the various inputs like connection factory, second-level cache, Dialect, Generators, InMemory metadata, and e.t.c. is an example of the use case of Builder Design Pattern.

Builder Design Pattern in Java Example

Let us understand with an example to create House through Builder Design Pattern. The house is nothing but a collection of different individual items like the basement, roof, structure, interior, and e.t.c. The house construction process needs several sub-process like- creating the basement, creating the roof, creating the interior, create structure. In order to create a perfect house, this sub-process must be executed in a particular order. The order is:- Basement => Structure => Roof => Interior. 

When we want to have our own house, then first we go to the architect for getting the house plan. Once the house plan is ready then we submit this plan to the engineer, and he/she ask the suitable builder to create the house. The builder may be of various types like Concrete housebuilder, Wooden housebuilder, Marble housebuilder, Igloo housebuilder, and e.t.c.

Problem:- To build the house, we need to take help from different experts. A single person may not be good at all the process. If we think programmatically, then a house creation is divided into multiple sub-object creations and then put together. 

Solution:- Use builder design pattern, which will simplify a complex object creation process, and which is easy to maintain and extend. The same creation process will create different implementation class objects.

Solution Using Builder Design Pattern

To create a House object we need the following sub-objects,

  • Basement object:- (ConcreteBasement, WoodenBasement, IceBasement, and e.t.c.)
  • Roof object:- (ConcreteRoof, WoodenRoof, IceRoof, and e.t.c.)
  • Structure object:- (ConcreteStructure, IceStructure, WoodenStructure, and e.t.c.)
  • Interior object:- (WoodInterior, MarbleInterior, GlassInterior, MixInterior, and e.t.c.)

Note:- we will use the character ‘I’ at the starting of the interface name. There will be many classes and interfaces in this example and they may create confusion therefore we will use this recommendation for the interfaces.

The different packages in this example are,

  • com.kp.product
  • com.kp.builder
  • com.kp.factory
  • com.kp.director
  • com.kp.test

For the better understanding purpose here we have given only an important part of the code. You can find the complete example code at GitHub:- BuilderDP.

Step1:- Create product and required classes for them. Develop an interface for each sub-process and create its implementation class. Later create a House class to use them.

The classes and interfaces in com.kp.product package,

  • IBasement.java (I)
  • WoodenBasement.java
  • ConcreteBasement.java
  • IceBasement.java
  • IronBasement.java
  • IStructure.java (I)
  • ConcreteStructure.java
  • IronStructure.java
  • IceStructure.java
  • WoodenStructure.java
  • IRoof.java (I)
  • IceRoof.java
  • ConcreteRoof.java
  • WoodenRoof.java
  • IInterior.java (I)
  • WoodenInterior.java
  • GlassInterior.java
  • MixedInterior.java
  • IceCarvingInterior.java
  • House.java

In the com.kp.product package the IBasement, IStructure, IRoof, and IInterior are the interfaces. The  WoodenBasement, ConcreteBasement, IceBasement, and IronBasement classes implement IBasement interface. Similarly, the ConcreteStructure, IronStructure, IceStructure, and WoodenStructure classes implement IStructure interface. The IceRoof, ConcreteRoof, and WoodenRoof classes implement IRoof interface. And, the WoodenInterior, GlassInterior, MixedInterior, and IceCarvingInterior classes implement IInterior interface. The House is the main product that contains methods to construct the house.

// IBasement.java
package com.kp.product;
public interface IBasement {
}
// IStructure.java
package com.kp.product;
public interface IStructure {
}
// IRoof.java
package com.kp.product;
public interface IRoof {
}
// IInterior.java
package com.kp.product;
public interface IInterior {
}
// House.java (Product - Complex object)
package com.kp.product;

public class House {

  private IBasement basement;
  private IStructure structure;
  private IRoof roof;
  private IInterior interior;
  
  // setter methods
  public void setBasement(IBasement basement) {
    this.basement = basement;
  }
  public void setStructure(IStructure structure) {
    this.structure = structure;
  }
  public void setRoof(IRoof roof) {
    this.roof = roof;
  }
  public void setInterior(IInterior interior) {
    this.interior = interior;
  }

  // getter methods :- not required 
  
  // toString() method
  @Override
  public String toString() {
    return "House [basement=" + basement + ", structure=" 
           + structure + ", roof=" + roof + ", interior=" 
           + interior + "]";
  }
}

Step2:- Develop a Builder interface. It will actually build House and It also contains a method for retrieving the product object (i.e. getHouse()).

com.kp.builder
  => IHouseBuilder.java (I)
  => MarbleHouseBuilder.java
  => WoodenHouseBuilder.java
  => IglooHouseBuilder.java
  => ConcreteHouseBuilder.java

Here IHouseBuilder is an interface and remaining are their implementation classes.

// IHouseBuilder.java (Builder interface)
package com.kp.builder;
import com.kp.product.House;
public interface IHouseBuilder {
   // plans
   public void constructRoof();
   public void constructBasement();
   public void constructStructure();
   public void constructInterior();
   public House getHouse();
}

You may have varieties of Builders like marble housebuilders, igloo housebuilders, bamboo house builders, and e.t.c. For each builder define one implementation, which contains logic to build the house. Builder will create the Object of a House, sets the basement, interior, roof, Structure, and return that house using getHouse().

Generally, a wooden house contains:- a wooden basement, wooden structure, wooden roof, and wooden interior. WoodenHouseBuilder class is implemented as given below.

// WoodenHouseBuilder.java (ConcreteBuilder i.e. Concrete class) 
package com.kp.builder;
import com.kp.product.House;
import com.kp.product.WoodenBasement;
import com.kp.product.WoodenInterior; 
import com.kp.product.WoodenRoof;
import com.kp.product.WoodenStructure;
public class WoodenHouseBuilder implements IHouseBuilder {
   private House house;
   
   public WoodenHouseBuilder(House house) {
      this.house = house;
      System.out.println("WoodenHouseBuilder: 1-param constructor");
   }

   @Override
   public void constructRoof() {
      house.setRoof(new WoodenRoof());
   }

   @Override
   public void constructBasement() {
      house.setBasement(new WoodenBasement());
   }

   @Override
   public void constructStructure() {
      house.setStructure(new WoodenStructure());
   }

   @Override
   public void constructInterior() {
      house.setInterior(new WoodenInterior());
   }

   @Override
   public House getHouse() {
      return house;
   }

}

Step3:- Develop a delegate/Director class, that will use one of the Builder to create the house. (In com.kp.director package)

//  CivilEngineer.java (Builder DP)
package com.kp.director;
import com.kp.builder.IHouseBuilder;
import com.kp.product.House;
public class CivilEngineer {
   private IHouseBuilder builder;

   public CivilEngineer(IHouseBuilder builder) {
      this.builder = builder;
      System.out.println("CivilEngineer.CivilEngineer()");
   }
   
   /* Builder design pattern main logic:-
    * Defining process to construct the complex object
    */
   public House buildHouse() {
      // process
      builder.constructBasement(); // 1st construct basement
      builder.constructStructure(); // 2nd construct structure
      builder.constructRoof(); // 3rd construct roof
      builder.constructInterior(); // 4th construct interior
      
      // get House object
      House house = builder.getHouse();
      
      // return final product
      return house;
   }
}

Step4:- Create a Factory to build the houses, use Factory Pattern. (In com.kp.factory package)

//  HouseFactory.java 
package com.kp.factory;
import com.kp.builder.ConcreteHouseBuilder;
import com.kp.builder.IHouseBuilder;
import com.kp.builder.IglooHouseBuilder;
import com.kp.builder.MarbleHouseBuilder;
import com.kp.builder.WoodenHouseBuilder;
import com.kp.director.CivilEngineer;
import com.kp.product.House;

// Factory Pattern Implementation
public class HouseFactory {
   // factory method
   public static House getInstance(String type) {
      House house = new House();
      IHouseBuilder builder = null;
      
      // create one of the several class object
      if(type.equalsIgnoreCase("wood")) {
         builder = new WoodenHouseBuilder(house);
      } else if(type.equalsIgnoreCase("ice")) {
         builder = new IglooHouseBuilder(house);
      } else if(type.equalsIgnoreCase("concrete")) {
         builder = new ConcreteHouseBuilder(house);
      } else if(type.equalsIgnoreCase("marble")) {
         builder = new MarbleHouseBuilder(house);
      } else {
         throw new IllegalArgumentException("Invalid House type");
      }
      
      // create director/delegator object
      CivilEngineer engineer = new CivilEngineer(builder);
      // build house
      house = engineer.buildHouse();
      
      // return house
      return house;
   }

}

Step5:- Develop a client application, which will use Builder and Engineer together to develop the house. (In com.kp.test package)

//  Customer1.java
package com.kp.test;
import com.kp.factory.HouseFactory;
import com.kp.product.House;
// Customer-1 wants Marble house
public class Customer1 {
   public static void main(String[] args) {
      House house = HouseFactory.getInstance("Marble");
      System.out.println(house);
   }
}

Output for the Customer-1:-

MarbleHouseBuilder: 1-param constructor
CivilEngineer.CivilEngineer()
ConcreteBasement:: 0-param constructor
IronStructure: 0-param constructor
ConcreteRoof: 0-param Constructor
MixedInterior: 0-param constructure
House [basement=ConcreteBasement [], structure=IronStructor [], roof=ConcreteRoof [], interior=MixedInterior []]

//  Customer2.java
package com.kp.test;
import com.kp.factory.HouseFactory;
import com.kp.product.House;
// Customer-2 wants wooden house
public class Customer2 {
   public static void main(String[] args) {
      House house = HouseFactory.getInstance("wood");
      System.out.println(house);
   }
}

Output for the Customer-2:-

WoodenHouseBuilder: 1-param constructor
CivilEngineer.CivilEngineer()
WoodenBasement:: 0-param constructor
WoodenStructor: 0-param constructor
WoodenRoof: 0-param Constructor
WoodenInterior: 0-param constructure
House [basement=WoodenBasement [], structure=WoodenStructor [], roof=WoodenRoof [], interior=WoodenInterior []]

Use case of Builder Design Pattern

Pre-defined Builder Pattern Implementation,

  • java.lang.StringBuilder#append() (unsynchronized)
  • java.lang.StringBuffer#append() (synchronized)
  • java.nio.ByteBuffer#put() (also on CharBuffer, ShortBuffer, IntBuffer, LongBuffer, FloatBuffer and DoubleBuffer)
  • javax.swing.GroupLayout.Group#addComponent()
  • All implementations of java.lang.Appendable
  • java.util.stream.Stream.Builder

In Hibernate,

Configuration cfg = new Configuration();
cfg.configure("com/kp/cfgs/Hibernate.cfg.xml");
SessionFactory factory = cfg.buildSessionFactory();

Here, buildSessionFactory() is given based on the builder design pattern. In the XML we give the information about individual objects like Dialect, Generator, Connection Provider, Second level cache, and e.t.c. All those individual objects will be created based on that data, and finally SessionFactory object i.e. the main product is created.

If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!

Leave a Comment

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