Builder Design Pattern in Java

Builder Design Pattern in Java with Examples

In this article, I am going to discuss the Builder Design Pattern in Java with Examples. Please read our previous article where we discussed Prototype Design Patterns in Java with Examples. The Builder Design Pattern falls under the category of the Creational Design Pattern. In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Builder design pattern, emphasizing its significance in simplifying the creation of complex objects and enhancing code readability.

What is a Builder Design Pattern?

In software development, constructing complex objects with many configuration options can be a daunting task. The Builder design pattern provides a solution by separating the construction of an object from its representation. By using a builder class, clients can easily configure and build complex objects step by step, without the need for multiple constructors or complex parameter lists

According to GOF, the Builder Design Pattern builds a complex object using many simple objects and using a step-by-step approach. The Process of constructing a complex object should be generic so that the same construction process can be used to create different representations of the same complex object.

So, the Builder Design Pattern is all about separating the construction process from its representation. When the construction process of your object is very complex then only you need to use to Builder Design Pattern.

Understanding the UML (or Class) Diagram of Builder Design Pattern in Java

Let us understand the UML or Class Diagram and understand the different components involved in the Builder Design Pattern. In order to understand this please have a look at the following diagram.

Understanding the UML (or Class) Diagram of Builder Design Pattern in Java

The Builder design pattern is a creational pattern that separates the construction of an object from its representation. In order to separate the construction process from its representation, the builder design pattern Involve four components. They are as follows.

  • Product: Represents the complex object being constructed.
  • Builder: Defines an interface for building the parts of the product. It provides methods for configuring and constructing the object.
  • Concrete Builders: Implement the builder interface and provide the specific implementation for building the parts of the product.
  • Director: Coordinates the building steps defined by the builder to construct the final object.
Example to Understand Builder Design Pattern in Java

Let’s consider a real-world example where the Builder pattern can be applied: a pizza ordering application. In this scenario, the application needs to create an order with various components such as pizza and drinks. Each order may have different combinations and configurations of these components.

The Builder pattern can be used to create a flexible and intuitive way to do this. The OrderBuilder class will be responsible for coordinating the building process.

The client code will use the OrderBuilder class to orchestrate the construction process. It will provide the necessary parameters and configurations to the OrderBuilder, which will utilize the appropriate builder classes to construct the web page object. The Director can apply specific sequences or rules for constructing different types of web pages.

By using the Builder pattern, the pizza ordering application can achieve several benefits. Firstly, it allows the construction of an order with different configurations without making the client code overly complex or tightly coupled to the construction process. Secondly, it provides a clear separation of concerns, where the client code focuses on specifying the desired parameters and the builders handle the construction details. Additionally, the pattern promotes code reusability, as the same builder classes can be used to construct different types of web pages. The UML Diagram of this example is given below using Builder Design Pattern.

Example to Understand Builder Design Pattern in Java

Implementing Builder Design Pattern in Java

Step 1: Create a new directory to store all the class files of this project.

Step 2: Open VS Code and create a new project, called Builder.

Step 3: In the project, create a new file called Packing.java. Add the following lines of code into Packing.java:

Implementing Builder Design Pattern in Java

In this file, we have added a function definition for pack(). We have not added any implementation to the function. This is because this file is an interface and not a class. Hence, this function will later be implemented by other classes.

Step 4: In the project, create a new file called Food.java. Add the following lines of code into Food.java:

Builder Design Pattern in Java with Examples

In this file, we have added three function definitions. We have not added any implementation for these functions. This is because this file is an interface and not a class. Hence, these functions will later be implemented by other classes.

Step 5: In the project, create a new file called Box.java. Add the following lines of code into Box.java:

What is a Builder Design Pattern?

This file implements the Packing class. It adds the implementation for the pack() method.

Step 6: In the project, create a new file called Can.java. Add the following lines of code into Can.java:

UML Diagram of Builder Design Pattern in Java

This file implements the Packing class. It adds the implementation for the pack() method.

Step 7: In the project, create a new file called Pizza.java. Add the following lines of code into Pizza.java:

Example to Understand Builder Design Pattern in Java

This abstract class implements the Food class. It adds the implementation for the packing() method. However, no implementation is provided for the price() method.

Step 8: In the project, create a new file called Drink.java. Add the following lines of code into Drink.java:

Implementing Builder Design Pattern in Java

This abstract class implements the Food class. It adds the implementation for the packing() method. However, no implementation is provided for the price() method.

Step 9: In the project, create a new file called VegPizza.java. Add the following lines of code into VegPizza.java:

Builder Pattern in Java with Examples

This class implements the Pizza abstract class. It adds the implementation for the name() and price() methods.

Step 10: In the project, create a new file called NonVegPizza.java. Add the following lines of code into NonVegPizza.java:

What is a Builder Pattern?

This class implements the Pizza abstract class. It adds the implementation for the name() and price() methods.

Step 11: In the project, create a new file called Juice.java. Add the following lines of code into Juice.java:

UML Diagram of Builder Pattern in Java

This class implements the Drink abstract class. It adds the implementation for the name() and price() methods.

Step 12: In the project, create a new file called Cola.java. Add the following lines of code into Cola.java:

Example to Understand Builder Pattern in Java

This class implements the Drink abstract class. It adds the implementation for the name() and price() methods.

Step 13: In the project, create a new file called Order.java. Add the following lines of code into Order.java:

Implementing Builder Pattern in Java

This file represents an order placed by the user. It has a list of food, which will be used to store the order placed. Furthermore, it also has three methods:

  • add(): This function takes an argument of type Food. This function is responsible for adding the food to the order list.
  • getTotalPrice(): This function calculates the total price of all the food items and returns it.
  • printOrder(): This function prints the list of food.

Step 14: In the project, create a new file called OrderBuilder.java. Add the following lines of code into OrderBuilder.java:

Builder Design Pattern in Java with Examples

In this class, there is a singular function, called buildOrder(). This function takes in two integer parameters, which represent the choice of pizza and choice of drink respectively. Based on these integers, the order is created. This order is then returned.

Step 15: In the project, create a new file called BuilderPatternDemo.java. This file will contain the main() function. Add the following lines of code into BuilderPatternDemo.java:

What is a Builder Design Pattern?

In the main function, the following steps are followed:

  1. The user is asked which type of pizza they would like.
  2. The user is asked which drink they would like.
  3. Based on the aforementioned user inputs, the order is created using the buildOrder() function in the OrderBuilder class.
  4. The order is printed out to the user, along with the total price of the items.

Step 16: Compile and execute the application. Ensure compilation is successful. Enter the required data and verify that the program works:

Builder Design Pattern in Java

Congratulations! You now know how to implement builder patterns!

UML Diagram of Builder Design Pattern

Now, let us see the Builder Design Pattern UML Diagram Component with our Example so that you can easily understand the UML Diagram.

UML Diagram of Builder Design Pattern

The classes can be described as follows:

  1. Builder: This class is responsible for building objects of the ObjectCombo class.
  2. ObjectCombo: This class contains multiple objects that form a collection.
  3. Object: This interface can be inherited to create abstract classes or concrete classes.
  4. ConcreteObject: These classes use the aforementioned interface to create concrete classes.
  5. DriverClass: This class contains the main() function and is responsible for handling the simulation of the program.
The Complete Example Code of the Builder Design Pattern using Java
Box.java
public class Box implements Packing
{
    @Override
    public String pack()
    {
        return "Box";
    }
}
BuilderPatternDemo.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class BuilderPatternDemo
{
    public static void main(String[] args) throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int pizzaChoice = 0, drinkChoice = 0;

        System.out.println();
        System.out.println("Choose a pizza: ");
        System.out.println("1. Veg Pizza");
        System.out.println("2. Non Veg Pizza");
        System.out.println();
        System.out.print("Enter your choice here: ");
        pizzaChoice = Integer.parseInt(br.readLine());

        System.out.println();
        System.out.println("Choose a drink: ");
        System.out.println("1. Fruit Juice");
        System.out.println("2. Cola");
        System.out.println();
        System.out.print("Enter your choice here: ");
        drinkChoice = Integer.parseInt(br.readLine());

        OrderBuilder orderBuilder = new OrderBuilder();
        Order o = orderBuilder.buildOrder(pizzaChoice, drinkChoice);

        System.out.println("Your order is: ");
        System.out.println();
        o.printOrder();
        System.out.println();
        System.out.println("Your total is: " + o.getTotalPrice());
    }    
}
Can.java
public class Can implements Packing
{
    @Override
    public String pack()
    {
        return "Can";
    }
}
Cola.java
public class Cola extends Drink
{
    @Override
    public String name()
    {
        return "Cola";
    }

    @Override
    public float price()
    {
        return 75;
    } 
}
Drink.java
public abstract class Drink implements Food
{
    @Override
    public Packing packing()
    {
        return new Can();
    }

    @Override
    public abstract float price();
}
Food.java
public interface Food
{
    public String name();
    public Packing packing();
    public float price();
}
Juice.java
public class Juice extends Drink
{
    @Override
    public String name()
    {
        return "Fruit Juice";
    }

    @Override
    public float price()
    {
        return 50;
    } 
}
NonVegPizza.java
public class NonVegPizza extends Pizza
{
    @Override
    public String name()
    {
        return "Non Veg Pizza";
    }

    @Override
    public float price()
    {
        return 150;
    }    
}
Order.java
import java.util.ArrayList;
import java.util.List;

public class Order
{
    private List<Food> order = new ArrayList<Food>();

    public void add (Food f)
    {
        order.add(f);
    }

    public float getTotalPrice ()
    {
        float price = 0;

        for (Food f : order)
            price += f.price();
        
        return price;
    }

    public void printOrder ()
    {
        int i = 1;

        for (Food f : order)
        {
            System.out.println(i + ". " + f.name() + ", Rs. " + f.price() + ".");
            System.out.println("Packed in: " + f.packing().pack());
            i++;
        }
    }
}
OrderBuilder.java
public class OrderBuilder
{
    public Order buildOrder(int pizzaChoice, int drinkChoice)
    {
        Order o = new Order();

        if (pizzaChoice == 1)       o.add(new VegPizza());
        else if (pizzaChoice == 2)  o.add(new NonVegPizza());

        if (drinkChoice == 1)       o.add(new Juice());
        else if (drinkChoice == 2)  o.add(new Cola());

        return o;
    }
}
Packing.java
public interface Packing
{
    public String pack();
}
Pizza.java
public abstract class Pizza implements Food
{
    @Override
    public Packing packing()
    {
        return new Box();
    }

    @Override
    public abstract float price();
}
VegPizza.java
public class VegPizza extends Pizza
{
    @Override
    public String name()
    {
        return "Veg Pizza";
    }

    @Override
    public float price()
    {
        return 100;
    }     
}
Advantages of Builder Design Pattern in Java

Some of the advantages of using the builder pattern are:

  • Simplified Object Construction: The Builder pattern simplifies the construction of complex objects by providing a step-by-step approach. Clients can use the builder’s methods to set the desired configuration options of the object, eliminating the need for multiple constructors or long parameter lists. This leads to cleaner, more readable code.
  • Fluent Interface: Builders often employ a fluent interface, allowing clients to chain method calls in a readable and expressive manner. This makes the code more intuitive and self-explanatory, enhancing the developer experience and promoting code maintainability.
  • Configurable Object Creation: Builders allow clients to selectively configure and customize the object being built. Clients can choose the desired options and set them through builder methods, ensuring flexibility and adaptability in object creation.
  • Encapsulation of Construction Logic: The Builder pattern encapsulates the construction logic within the builder class. This keeps the construction details hidden from the client, reducing coupling and promoting the principle of encapsulation. Changes to the construction process can be localized to the builder class without impacting the client code.
  • Consistent Object Creation: The Builder pattern ensures that the object is created in a consistent and valid state. Builders can enforce rules and constraints during the construction process, leading to the creation of objects with consistent configurations.
Disadvantages of Builder Design Pattern in Java

Some of the disadvantages of using the builder pattern are:

  • Increased Complexity: The Builder pattern introduces additional complexity to the codebase. It requires the creation of multiple classes (builder, product, director) and the establishment of relationships between them. This can add cognitive overhead and make the codebase more intricate.
  • Overhead of Creating Builder Classes: Implementing builders for every complex object may increase the number of classes in the system. This can impact code readability and maintenance, especially when dealing with a large number of complex objects.
  • Limited Usefulness for Simple Objects: The Builder pattern may be overkill for simple objects that require only a few configuration options. In such cases, using a builder might introduce unnecessary complexity and code verbosity.
  • Dependency on Director: The Builder pattern relies on the director class to coordinate the building steps. This introduces a dependency on the director, potentially limiting the reusability of the builder without the director.
  • Object Immutability: The Builder pattern does not inherently guarantee the immutability of the constructed objects. Clients may need to take additional steps to enforce immutability if it is a requirement.

The Builder design pattern provides valuable advantages in simplifying the construction of complex objects, enhancing code readability, and promoting flexible and configurable object creation. Its benefits include simplified object construction, fluent interface, configurable object creation, encapsulation of construction logic, and consistent object creation. However, developers should be aware of potential drawbacks, such as increased complexity, the overhead of creating builder classes, limited usefulness for simple objects, dependency on the director, and the need for additional steps to ensure object immutability. By carefully evaluating the complexity and configurability of objects, developers can effectively utilize the Builder pattern to streamline the construction process and improve code maintainability.

In the next article, I am going to discuss Object Pool Design Pattern in Java with Examples. Here, in this article, I try to explain Builder Design Pattern in Java with Examples. I hope you understood the need for and use of Builder Design Pattern in Java.

Leave a Reply

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