Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview

The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates the construction of a complex object from its representation so that the same construction process can create different representations.

This pattern is particularly useful when an object needs to be created with many possible configurations. It provides a way to construct the object gradually by using a separate Builder class.

In the context of the Builder design pattern, the “construction” of an object refers to the process of creating and initializing an instance of a class. The “representation” of an object, on the other hand, refers to the final state of the object once it has been constructed.

When we say that the Builder pattern “separates the construction of an object from its representation”, we mean that the process of creating an object is decoupled from the specific state that the object is in once it’s created.

This separation allows the same construction process to create different representations. In other words, by changing the parameters we pass to the builder, we can create objects with different states using the same construction process.

Here’s a simple example to illustrate this:

Java
Pizza margherita = new Pizza.Builder()
    .setDough("thin")
    .setSauce("tomato")
    .setTopping("mozzarella")
    .build();

Pizza hawaiian = new Pizza.Builder()
    .setDough("thick")
    .setSauce("tomato")
    .setTopping("ham+pineapple")
    .build();

In this example, we’re using the same Pizza.Builder class to construct two different types of pizzas: Margherita and Hawaiian. The construction process is the same (i.e., we’re calling the same methods on the builder), but the representations of the objects (i.e., the final pizzas) are different because we’re passing different parameters to the builder.

House example with the Builder pattern:

Java
package design_pattern.creational.builder_design_pattern.demo1;
public class House {
    private int floors;
    private  int rooms;
    private boolean hasGarden;
    private boolean hasPool;
    private boolean hasGarage;

    private House() {
        // private constructor to enforce object creation through builder
    }

    public static class Builder {
        private House house;
        public static Builder newInstance() {
            return new Builder();
        }

        public Builder() {
            house = new House();
        }

        public Builder setFloors(int floors) {
            house.floors = floors;
            return this;
        }

        public Builder setRooms(int rooms) {
            house.rooms = rooms;
            return this;
        }

        public Builder setHasGarden(boolean hasGarden) {
            house.hasGarden = hasGarden;
            return this;
        }

        public Builder setHasPool(boolean hasPool) {
            house.hasPool = hasPool;
            return this;
        }

        public Builder setHasGarage(boolean hasGarage) {
            house.hasGarage = hasGarage;
            return this;
        }

        public House build() {
            return house;
        }
    }
}




// Creating a House object with Builder pattern
public class Client {
    public static void main(String[] args) {
        House house = new House.Builder()
                .setFloors(2)
                .setRooms(3)
                .setHasGarden(true)
                .setHasPool(false)
                .setHasGarage(true)
                .build();
    }
}

In this example:

  • Construction refers to the process of creating a House object. This is done by calling various set methods on the Builder object (like setFloors, setRooms, etc.), and then calling build to finalize the construction.
  • Representation refers to the final House object that is created. This includes all the details of the house like the number of floors, number of rooms, whether it has a garden, a pool, a garage, etc.

The Builder pattern “separates the construction of an object from its representation” because the process of creating a House object (i.e., calling set methods on the Builder) is separate from the final House object that is created (i.e., the result of the build method).

This separation allows the same construction process to create different representations. For example, you can use the same Builder methods to create a House object with 2 floors, 3 rooms, and a garden, or a House object with 1 floor, 4 rooms, and a garage. The construction process is the same, but the final House objects (i.e., the representations) are different.

Steps to Create BuilderPattern

Here’s an example of the Builder pattern in Java, demonstrating how to construct a complex Car object:

Step 1: Define the Product Class

First, we define the Car class, which is the complex object that we want to construct.

Java
public class Car {
    private String make;
    private String model;
    private int year;
    private double engineCapacity;
    // Constructor is private to force the use of the Builder
    private Car(Builder builder) {
        this.make = builder.make;
        this.model = builder.model;
        this.year = builder.year;
        this.engineCapacity = builder.engineCapacity;
    }
    // Getters
    public String getMake() { return make; }
    public String getModel() { return model; }
    public int getYear() { return year; }
    public double getEngineCapacity() { return engineCapacity; }
    // Static Builder class
    public static class Builder {
        private String make;
        private String model;
        private int year;
        private double engineCapacity;
        public Builder setMake(String make) {
            this.make = make;
            return this;
        }
        public Builder setModel(String model) {
            this.model = model;
            return this;
        }
        public Builder setYear(int year) {
            this.year = year;
            return this;
        }
        public Builder setEngineCapacity(double engineCapacity) {
            this.engineCapacity = engineCapacity;
            return this;
        }
        public Car build() {
            return new Car(this);
        }
    }
}

Step 2: Use the Builder to Create an Object

The client code uses the Builder class to create an instance of the Car class.

Java
public class BuilderPatternDemo {
    public static void main(String[] args) {
        Car car = new Car.Builder()
                .setMake("Honda")
                .setModel("Civic")
                .setYear(2020)
                .setEngineCapacity(1.8)
                .build();
        System.out.println("Car constructed: " + car.getMake() + " " + car.getModel());
    }
}

In this example, the Car class has a nested static Builder class with methods to set the properties of the Car. Each method returns the Builder object itself, allowing for method chaining. The build() method constructs the Car instance with the current state of the builder.

This pattern allows for clear separation between the construction and representation of an object, enables code reuse with an object-oriented way to construct complex objects, and makes it easy to introduce new parameters to the object construction without changing all of the places where the object is created.

Why Builder Methods Should Return the Builder Instance 🏗️

In Java, the Builder pattern is a popular design pattern for constructing complex objects. It provides a clear and flexible way to construct an object step by step, and it’s especially useful when an object has many parameters. 📚

The Problem with Void Setter Methods ❌

Consider the following Builder class:

Java
public class Builder{
    private String attribute;

    public void setAttribute(String attribute) {
        this.attribute = attribute;
    }
    
    public Object build(){
        return new Object(this);
    }
}

In this case, the setAttribute method is void, meaning it doesn’t return anything. If you want to set the attribute and then build the object, you’d have to do it in two steps:

Java
Builder builder = new Builder();
builder.setAttribute("value");
Object object = builder.build();

This works, but it’s not very elegant or readable, especially if you have many attributes to set. 😕

The Solution: Return the Builder Instance ✔️

Now, let’s change the setAttribute method so that it returns the Builder instance:

Java
public class Builder{
    private String attribute;

    public Builder setAttribute(String attribute) {
        this.attribute = attribute;
        return this;
    }
    
    public Object build(){
        return new Object(this);
    }
}

With this change, you can now chain the method calls together:

Java
Object object = new Builder().setAttribute("value").build();

This is much cleaner and more readable. It’s also more flexible, as you can easily add more attributes to set without making the code more complex. 🎉

Why Builder class is made of static?

The Builder class is made static inside the host class (e.g., ‘Car’ in the provided example) for several reasons:

1. Independence from the Outer Class Instance: A static nested class does not require an instance of the outer class to be created. This is particularly useful for the Builder pattern because you often want to create an instance of the product class (‘Car’ in this example) without having an existing instance of the outer class. Making the Builder class static allows it to be instantiated independently of the outer class.

2. Encapsulation: By defining the Builder class as a static nested class within the product class, it is kept within the namespace of the product class, which is a good encapsulation practice. It logically groups the Builder class with its associated product class without requiring an instance of the product class to exist.

3. Access to Private Members of the Outer Class: A static nested class has access to the private members (fields, constructors, and methods) of the outer class. This is beneficial for the Builder pattern, as the Builder often needs to access private constructors or fields of the product class to construct an instance. This access allows the Builder to fully manage the instantiation process, including setting values for private fields without exposing these fields directly through public setters.

4. Simplifying the Client Code: Since the Builder is a static nested class, the client code can instantiate it without first having to instantiate the outer class. This makes the client code cleaner and more straightforward, as it can directly call `new Car.Builder()` without needing an instance of `Car`.

5. Clarity and Readability: Making the Builder class static and nesting it within the product class helps to clarify that this Builder is specifically for constructing instances of the outer class. It organizes the code in a way that makes the relationship between the Builder and the product class clear, improving the readability and maintainability of the code.

In summary, making the Builder class static and nesting it within the product class leverages several advantages in terms of design and usability, making it a common practice in implementing the Builder pattern in Java.

Why does the Product class have a private constructor?

In the Builder Design Pattern, the Car class has a private constructor to enforce the use of the Builder class for creating Car objects. Here’s why:

  1. Encapsulation: The private constructor ensures that the Car class’s fields can only be set through the Builder. This encapsulates the construction logic within the Builder and keeps the Car class simple and focused on its primary responsibilities.
  2. Flexibility: The Builder allows for flexible and readable object creation. You can set only the fields you’re interested in and leave the rest with default values. This wouldn’t be possible with a public constructor without ending up with a large number of constructor overloads.
  3. Immutability: Once a Car object is created, it can’t be changed. This is a desirable property in multi-threaded environments where mutable objects can lead to issues.
  4. Robustness: The build() method in the Builder can check the validity of the parameters before creating the object. This ensures that the Car object is always in a valid state.

Understanding the Benefits of the Builder Design Pattern

Here are some scenarios where the Builder pattern can be useful:

  1. Complex Objects: The Builder design pattern is a good choice when you’re working with complex objects that should be created in a step-by-step fashion. It’s especially useful when an object requires many parameters in its constructor.
  2. Immutable Objects: The Builder design pattern can be used to construct immutable objects. Once the object is constructed, it cannot be changed. This is useful in multi-threaded environments where mutable objects can lead to issues.
  3. Configurable Objects: If an object has many configuration options, the Builder pattern can make it easier to create instances of that object. Instead of having to remember the order of parameters in a constructor, you can just specify the name of the configuration option.
  4. Readability: The Builder pattern can improve the readability of your code. By using method chaining (a series of methods that return the builder object), you can make your code more intuitive and easy to understand.
Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over
Blog

Understanding JWT: A Comprehensive Guide to Authentication and Security

What is JWT? JWT stands for JSON Web Token. It is a standard (RFC 7519) that defines a compact and