Builder Design Pattern

What is Builder Design Pattern and it’s using advantage in java

What is Builder Design Pattern?

The Builder Pattern is a creational design pattern that separates the construction of a complex object from its representation, allowing the same construction process to create different representations. This pattern is particularly useful when an object needs to be constructed with many possible configurations or when the construction steps must be carried out in a specific order.

Here’s an example of the Builder Pattern in Java using a Computer class as the complex object:

Example# 1

Example 1
/*
 * Author: Zameer Ali Mohil
 * */

// Product class
class Computer {
    private String CPU;
    private String RAM;
    private String storage;
    private String GPU;

    public Computer(String CPU, String RAM, String storage, String GPU) {
        this.CPU = CPU;
        this.RAM = RAM;
        this.storage = storage;
        this.GPU = GPU;
    }

    // Getters for various components
    public String getCPU() {
        return CPU;
    }

    public String getRAM() {
        return RAM;
    }

    public String getStorage() {
        return storage;
    }

    public String getGPU() {
        return GPU;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", RAM='" + RAM + '\'' +
                ", storage='" + storage + '\'' +
                ", GPU='" + GPU + '\'' +
                '}';
    }
}

// Builder interface
interface ComputerBuilder {
    void buildCPU(String CPU);
    void buildRAM(String RAM);
    void buildStorage(String storage);
    void buildGPU(String GPU);
    Computer getResult();
}

// Concrete builder class
class ConcreteComputerBuilder implements ComputerBuilder {
    private Computer computer;

    public ConcreteComputerBuilder() {
        this.computer = new Computer("Default CPU", "Default RAM", "Default Storage", "Default GPU");
    }

    @Override
    public void buildCPU(String CPU) {
        computer.CPU = CPU;
    }

    @Override
    public void buildRAM(String RAM) {
        computer.RAM = RAM;
    }

    @Override
    public void buildStorage(String storage) {
        computer.storage = storage;
    }

    @Override
    public void buildGPU(String GPU) {
        computer.GPU = GPU;
    }

    @Override
    public Computer getResult() {
        return computer;
    }
}

// Director class
class Director {
    public Computer constructGamingComputer(ComputerBuilder builder) {
        builder.buildCPU("Intel Core i9");
        builder.buildRAM("32GB DDR4");
        builder.buildStorage("1TB SSD");
        builder.buildGPU("NVIDIA GeForce RTX 3080");
        return builder.getResult();
    }

    public Computer constructOfficeComputer(ComputerBuilder builder) {
        builder.buildCPU("Intel Core i5");
        builder.buildRAM("16GB DDR4");
        builder.buildStorage("500GB HDD");
        builder.buildGPU("Integrated Graphics");
        return builder.getResult();
    }
}

// Client code
public class BuilderPatternExample {
    public static void main(String[] args) {
        // Using the Builder Pattern

        // Create a builder
        ConcreteComputerBuilder builder = new ConcreteComputerBuilder();

        // Create a director
        Director director = new Director();

        // Construct a gaming computer
        Computer gamingComputer = director.constructGamingComputer(builder);
        System.out.println("Gaming Computer:\n" + gamingComputer);

        // Construct an office computer
        Computer officeComputer = director.constructOfficeComputer(builder);
        System.out.println("Office Computer:\n" + officeComputer);
    }
}

In this example:

  • The Computer class is the complex object that we want to construct.
  • The ComputerBuilder interface declares methods for building different components of the computer.
  • The ConcreteComputerBuilder class is a specific implementation of the builder interface. It constructs a Computer object and allows for customizing its components.
  • The Director class provides a higher-level interface for constructing complex objects. It specifies the construction steps using a builder.
  • The BuilderPatternExample class demonstrates how to use the builder pattern to create different types of computers.

This example illustrates how the Builder Pattern allows for the construction of complex objects with various configurations, while keeping the construction process separate from the representation of the object. The director class defines the steps for building different types of computers, and the concrete builder class implements those steps, producing the final product.

Example# 2:

Let’s consider a simplified example of a Meal class, and we’ll use the Builder Pattern to construct different types of meals.

Builder Design Pattern

Example
// Product
class Meal {
    private String starter;
    private String mainCourse;
    private String dessert;

    public void setStarter(String starter) {
        this.starter = starter;
    }

    public void setMainCourse(String mainCourse) {
        this.mainCourse = mainCourse;
    }

    public void setDessert(String dessert) {
        this.dessert = dessert;
    }

    public void showItems() {
        System.out.println("Starter: " + starter);
        System.out.println("Main Course: " + mainCourse);
        System.out.println("Dessert: " + dessert);
    }
}

// Builder Interface
interface MealBuilder {
    void buildStarter();
    void buildMainCourse();
    void buildDessert();
    Meal getMeal();
}

// Concrete Builder
class HealthyMealBuilder implements MealBuilder {
    private Meal meal = new Meal();

    @Override
    public void buildStarter() {
        meal.setStarter("Salad");
    }

    @Override
    public void buildMainCourse() {
        meal.setMainCourse("Grilled Chicken");
    }

    @Override
    public void buildDessert() {
        meal.setDessert("Fruit Salad");
    }

    @Override
    public Meal getMeal() {
        return meal;
    }
}

// Director
class Waiter {
    private MealBuilder mealBuilder;

    public Waiter(MealBuilder mealBuilder) {
        this.mealBuilder = mealBuilder;
    }

    public void constructMeal() {
        mealBuilder.buildStarter();
        mealBuilder.buildMainCourse();
        mealBuilder.buildDessert();
    }
}

// Client
public class BuilderPatternExample {
    public static void main(String[] args) {
        MealBuilder healthyMealBuilder = new HealthyMealBuilder();
        Waiter waiter = new Waiter(healthyMealBuilder);

        waiter.constructMeal();
        Meal meal = healthyMealBuilder.getMeal();

        System.out.println("Healthy Meal:");
        meal.showItems();
    }
}

1. Define the Product Class:

Meal class
// Product class
class Meal {
    private String mainCourse;
    private String sideDish;
    private String drink;

    public Meal(String mainCourse, String sideDish, String drink) {
        this.mainCourse = mainCourse;
        this.sideDish = sideDish;
        this.drink = drink;
    }

    // Getters for different components
    public String getMainCourse() {
        return mainCourse;
    }

    public String getSideDish() {
        return sideDish;
    }

    public String getDrink() {
        return drink;
    }

    @Override
    public String toString() {
        return "Meal{" +
                "mainCourse='" + mainCourse + '\'' +
                ", sideDish='" + sideDish + '\'' +
                ", drink='" + drink + '\'' +
                '}';
    }
}

2. Define the Builder Interface:

MealBuilder Interface
// Builder interface
interface MealBuilder {
    void buildMainCourse(String mainCourse);
    void buildSideDish(String sideDish);
    void buildDrink(String drink);
    Meal getResult();
}

3. Create Concrete Builder:

HealthyMealBuilder implements MealBuilder
// Concrete builder class
class HealthyMealBuilder implements MealBuilder {
    private Meal meal;

    public HealthyMealBuilder() {
        this.meal = new Meal("Grilled Chicken Salad", "Steamed Vegetables", "Water");
    }

    @Override
    public void buildMainCourse(String mainCourse) {
        meal = new Meal(mainCourse, meal.getSideDish(), meal.getDrink());
    }

    @Override
    public void buildSideDish(String sideDish) {
        meal = new Meal(meal.getMainCourse(), sideDish, meal.getDrink());
    }

    @Override
    public void buildDrink(String drink) {
        meal = new Meal(meal.getMainCourse(), meal.getSideDish(), drink);
    }

    @Override
    public Meal getResult() {
        return meal;
    }
}

4. Create Director:

Waiter
// Director class
class Waiter {
    public Meal constructMeal(MealBuilder builder) {
        builder.buildMainCourse("Burger");
        builder.buildSideDish("Fries");
        builder.buildDrink("Cola");
        return builder.getResult();
    }
}

5. Client Code:

BuilderPatternExample
// Client code
public class BuilderPatternExample {
    public static void main(String[] args) {
        // Using the Builder Pattern

        // Create a builder
        HealthyMealBuilder builder = new HealthyMealBuilder();

        // Create a director
        Waiter waiter = new Waiter();

        // Construct a meal
        Meal meal = waiter.constructMeal(builder);
        System.out.println("Meal:\n" + meal);
    }
}

In this example:

  • The Meal class is the product that we want to construct.
  • The MealBuilder interface declares methods for building different components of the meal.
  • The HealthyMealBuilder class is a specific implementation of the builder interface. It constructs a Meal object and allows for customizing its components.
  • The Waiter class provides a higher-level interface for constructing meals. It specifies the construction steps using a builder.
  • The BuilderPatternExample class demonstrates how to use the builder pattern to create a meal.

This example illustrates how the Builder Pattern allows for the construction of complex objects with various configurations while keeping the construction process separate from the representation of the object. The director class defines the steps for building a meal, and the concrete builder class implements those steps, producing the final product.


What are advantages of using builder design pattern in java?

The Builder design pattern in Java provides several advantages, making it a useful pattern in various scenarios. Here are some of the key advantages:

1. Separation of Concerns: The Builder pattern separates the construction of a complex object from its representation. This separation allows for better organization of code and improves maintainability.

2. Flexibility: The pattern allows for the step-by-step construction of an object. This step-wise construction provides flexibility in choosing and omitting the construction steps based on specific requirements.

3. Complex Object Creation: When an object involves the construction of multiple parts with different configurations, the Builder pattern simplifies the process of creating and assembling these parts into a complete object.

4. Readability: The pattern improves code readability by providing clear methods for constructing different parts of an object. This makes the code more self-explanatory and easy to understand.

5. Encapsulation: The details of the construction process are encapsulated within the builder classes. This encapsulation helps in hiding the complexity of object creation and allows clients to interact with a concise and well-defined interface.

6. Consistent Object Creation: Builders ensure that an object is always in a consistent state, as the construction process is controlled and defined by the builder. This helps avoid the creation of partially initialized or invalid objects.

7. Variability: The pattern supports the creation of different variations of an object using the same construction process. By using different builders, you can create objects with different configurations.

8. Easier Testing: Testing can be simplified as each part of the construction process is encapsulated within the builder. It becomes easier to write unit tests for individual builders and ensure the correctness of each construction step.

9. Avoid Telescoping Constructors: The Builder pattern helps avoid the “telescoping constructors” anti-pattern, where constructors have a long list of parameters. With builders, parameters are set using method calls, making the code cleaner and more maintainable.

10. Immutable Objects: The Builder pattern can be used to create immutable objects by ensuring that once an object is constructed, its state cannot be changed. This can be beneficial for achieving thread-safety.

In summary, the Builder design pattern in Java promotes code organization, flexibility, and readability, especially in situations where the construction of an object involves multiple steps or configurations. It is particularly useful when dealing with complex object creation scenarios.

Leave a Reply

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