Learnitweb

Builder design pattern in Java

1. Introduction

The Builder Design Pattern is a Creational Design Pattern that provides a step-by-step approach to constructing complex objects. Instead of directly instantiating the object, the pattern separates the construction logic from the representation, allowing developers to create objects in a controlled, flexible, and readable manner.

2. Why use the builder design pattern?

  • Constructor Overload Problems: When an object has many parameters, especially optional ones, the number of constructors required can grow exponentially. This makes the code hard to read and maintain.
  • Code Readability: Builder pattern ensures that the code for creating an object is clear and self-documenting, which improves overall code readability.
  • Separation of Concerns: The object creation logic (builder) is separated from the object’s business logic, adhering to the Single Responsibility Principle.
  • Immutable Objects: It simplifies the creation of immutable objects by controlling how the object is constructed.

3. Components of the Builder pattern

  • Product: The complex object being built.
  • Builder: An abstract interface or class defining the steps to construct the product.
  • Concrete Builder: A class implementing the builder interface and providing specific implementations for constructing parts of the product.
  • Director (Optional): Coordinates the construction process by calling the appropriate builder methods. This is optional and often omitted when the client directly uses the builder.
  • Client: Initiates the building process, often specifying the desired configuration via the builder.

4. Detailed implementation in Java

Let’s take an example of building a Computer with both required and optional parameters.

Step 1: Define the Product

The product is the complex object being constructed, in this case, a Computer.

public class Computer {
    // Required parameters
    private String CPU;
    private String RAM;

    // Optional parameters
    private boolean hasGraphicsCard;
    private boolean hasBluetooth;

    // Private constructor ensures objects are created via the builder
    private Computer(ComputerBuilder builder) {
        this.CPU = builder.CPU;
        this.RAM = builder.RAM;
        this.hasGraphicsCard = builder.hasGraphicsCard;
        this.hasBluetooth = builder.hasBluetooth;
    }

    @Override
    public String toString() {
        return "Computer [CPU=" + CPU + ", RAM=" + RAM +
               ", Graphics Card=" + hasGraphicsCard + ", Bluetooth=" + hasBluetooth + "]";
    }

    // Static inner builder class
    public static class ComputerBuilder {
        // Required parameters
        private String CPU;
        private String RAM;

        // Optional parameters
        private boolean hasGraphicsCard;
        private boolean hasBluetooth;

        // Constructor for required parameters
        public ComputerBuilder(String CPU, String RAM) {
            this.CPU = CPU;
            this.RAM = RAM;
        }

        // Setter for optional parameter: Graphics Card
        public ComputerBuilder setGraphicsCard(boolean hasGraphicsCard) {
            this.hasGraphicsCard = hasGraphicsCard;
            return this;
        }

        // Setter for optional parameter: Bluetooth
        public ComputerBuilder setBluetooth(boolean hasBluetooth) {
            this.hasBluetooth = hasBluetooth;
            return this;
        }

        // Build method to create the final Computer object
        public Computer build() {
            return new Computer(this);
        }
    }
}

Step 2: Create the Client

The client uses the builder to create the desired object. Optional parameters are set using setter methods.

public class BuilderPatternDemo {
    public static void main(String[] args) {
        // Create a high-end computer
        Computer highEndComputer = new Computer.ComputerBuilder("Intel i9", "32GB")
                .setGraphicsCard(true)
                .setBluetooth(true)
                .build();

        System.out.println("High-End Computer: " + highEndComputer);

        // Create a budget computer
        Computer budgetComputer = new Computer.ComputerBuilder("Intel i3", "8GB")
                .setGraphicsCard(false)
                .setBluetooth(false)
                .build();

        System.out.println("Budget Computer: " + budgetComputer);
    }
}

Output

High-End Computer: Computer [CPU=Intel i9, RAM=32GB, Graphics Card=true, Bluetooth=true]
Budget Computer: Computer [CPU=Intel i3, RAM=8GB, Graphics Card=false, Bluetooth=false]

5. Advantages of Builder design pattern

  • Better Readability and Maintainability: The step-by-step approach for creating objects makes the code easier to read and maintain.
  • Handles Complex Objects: Great for objects with numerous parameters, especially when some are optional.
  • Prevents Constructor Overloading: Reduces the need for multiple overloaded constructors, simplifying the code.
  • Supports Immutable Objects: Ensures the constructed object is immutable, improving thread safety.
  • Customizable: Builders can be easily extended to add new parameters without altering the product or other parts of the builder.
  • Encapsulation of Construction Logic: Keeps the construction logic separate from the business logic of the object.

6. When to use the Builder design pattern?

  • Objects with Many Parameters: Example: Building configurations for hardware or vehicles with optional features.
  • Immutable Object Creation: Useful when creating objects that should not be modified after construction.
  • Different Representations: When the same construction process can produce different representations of an object.
  • Code Clarity: Enhances code readability when object construction involves complex logic or combinations of parameters.

7. Comparison with other patterns

AspectBuilder PatternFactory MethodPrototype
PurposeConstructs complex objects step by step.Creates objects through subclasses or factories.Clones existing objects to create new instances.
FocusFocus on object configuration and construction process.Focus on delegating object creation.Focus on duplicating existing objects.
ComplexityBest for objects with many optional parameters.Simpler than Builder for fewer parameters.Useful for creating many similar objects.

8. Real-World Examples

  • Android Development: The AlertDialog.Builder and Notification.Builder classes in Android use the builder pattern to configure and build dialogs or notifications.
  • StringBuilder in Java: The StringBuilder class in Java provides a fluent API for creating complex strings through appending operations.
  • HTTP Clients: Libraries like OkHttp and Apache HttpClient use the Builder pattern for configuring and building HTTP requests.
  • Vehicle Manufacturing: Used in scenarios where vehicles with various optional features (e.g., sunroof, GPS) are created.

9. Director Role (Optional)

In some implementations, a Director class is used to manage the construction process. The director uses the builder’s methods to create the product.

Example:

public class Director {
    public Computer constructHighEndComputer(Computer.ComputerBuilder builder) {
        return builder.setGraphicsCard(true)
                      .setBluetooth(true)
                      .build();
    }

    public Computer constructBudgetComputer(Computer.ComputerBuilder builder) {
        return builder.setGraphicsCard(false)
                      .setBluetooth(false)
                      .build();
    }
}