Builder design pattern - Java - Explained

Intent

Separate the construction of the complex object from its representation so that the same construction process can create different representations.
It's a creational design pattern.
The main idea behind this pattern is to provide a flexible solution for creating objects with different attributes or configurations.

Motivation behind the pattern/problem it solves?

Builder Design Pattern is used to solve the problem of creating complex objects with multiple parameters, where the order or presence of some parameters may vary.

Builder Pattern example

let's say we have an Order class with several attributes such as customer details, shipping address, payment method, and items. We could create a constructor with all of these parameters, but that would become very cumbersome to use and maintain as the number of attributes increases.
Instead, we can use the Builder Design Pattern to encapsulate the construction of an Order object, providing a more flexible solution. Here's an example implementation of the Order class using the Builder Pattern:

complete code (github link)

public class Order {
    private  Customer customer;
    private  Address shippingAddress;
    private  PaymentMethod paymentMethod;
    private  List<Item> items;

    private Double total=0.0;

    private Order(OrderBuilder builder) {
        this.customer = builder.customer;
        this.shippingAddress = builder.shippingAddress;
        this.paymentMethod = builder.paymentMethod;
        this.items = builder.items;
    }
    public static class OrderBuilder {
        private Customer customer;
        private Address shippingAddress;
        private PaymentMethod paymentMethod;
        private List<Item> items = new ArrayList<>();

        public OrderBuilder(Customer customer, Address shippingAddress) {
            this.customer = customer;
            this.shippingAddress = shippingAddress;
        }
        public OrderBuilder paymentMethod(PaymentMethod paymentMethod) {
            this.paymentMethod = paymentMethod;
            return this;
        }

        public OrderBuilder addItem(Item item) {
            this.items.add(item);
            return this;
        }

        public Order build() {
            return new Order(this);
        }

    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Address getShippingAddress() {
        return shippingAddress;
    }

    public void setShippingAddress(Address shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    public PaymentMethod getPaymentMethod() {
        return paymentMethod;
    }

    public void setPaymentMethod(PaymentMethod paymentMethod) {
        this.paymentMethod = paymentMethod;
    }

    public List<Item> getItems() {
        return items;
    }

    public void setItems(List<Item> items) {
        this.items = items;
    }

    public Double getTotal() {
        if (!this.getItems().isEmpty()) {
            this.total = this.getItems().stream().mapToDouble(item -> item.getPrice()*item.getQuantity()).sum();
        } else {
            this.total = total;
        }
        return this.total;
    }

}

*****
public class BuilderPatternMain {
    public static void main(String[] args) {

        // Create a new customer
        Customer customer = new Customer("Kiran Smith", "kiran.smith@example.com", "123-456-7890");

        // Create a shipping address
        Address shippingAddress = new Address("123 Main St", "Anytown", "CA", "12345");

        // Create an OrderBuilder and set the required attributes
        Order.OrderBuilder orderBuilder = new Order.OrderBuilder(customer, shippingAddress);

        // Add some items to the order
        Item item1 = new Item("Product 1", 10.0, 2);
        Item item2 = new Item("Product 2", 15.0, 1);
        orderBuilder.addItem(item1);
        orderBuilder.addItem(item2);

        // Set the payment method for the order
        PaymentMethod paymentMethod = new PaymentMethod("Visa", "1234567890123456", "12/24", "123");
        orderBuilder.paymentMethod(paymentMethod);

        // Build the order
        Order order = orderBuilder.build();

        // Display the order details
        System.out.println("Order details:");
        System.out.println("Customer name: " + order.getCustomer().getName());
        System.out.println("Shipping address: " + order.getShippingAddress().getStreetAddress());
        System.out.println("Payment method: " + order.getPaymentMethod().getName());

        System.out.println("Items:");
        for (Item item : order.getItems()) {
            System.out.println("- " + item.getName() + " x " + item.getQuantity() + " @ $" + item.getPrice());
        }

        System.out.println("Total: $" + order.getTotal());
    }
}

*** OUTPUT ***
Order details:
Customer name: Kiran Smith
Shipping address: 123 Main St
Payment method: Visa
Items:
- Product 1 x 2 @ $10.0
- Product 2 x 1 @ $15.0
Total: $35.0

In this example, we create a new customer and shipping address, and then use the OrderBuilder to add items to the order and set the payment method. Finally, we build the order and display the order details, including the customer name, shipping address, payment method, and item details with the total cost.

Structure of the builder pattern

reference : wiki

In the above UML class diagram, the Director class doesn't create and assemble the ProductA1 and ProductB1 objects directly. Instead, the Director refers to the Builder interface for building (creating and assembling) the parts of a complex object, which makes the Director independent of which concrete classes are instantiated (which representation is created). The Builder1 class implements the Builder interface by creating and assembling the ProductA1 and ProductB1 objects. (reference wikipedia)
In our example, BuilderPatternMain is our director class and orderBuilder is builder. Though in our case customer and shippingAddress entities are created in the Director class means BuilderPatternMain class those are associated to the order via Builder interface.
There are multiple ways we can utilize the builder pattern to create the complex object, In the above example we have used is simple builder example.
Another famous example is the Method Chaining example where the builder class provides methods that return the builder object itself, allowing the caller to chain multiple method calls together to set the object's fields.

MethodChaining Example builder pattern

package com.ecommercearchitect.designpatterns.builder.methodchainingexample;

public class Car {
    private String model;
    private String color;
    private int year;

    public static class Builder {
        private String model;
        private String color;
        private int year;

        public Builder() {}

        public Builder setModel(String model) {
            this.model = model;
            return this;
        }
        public Builder setColor(String color) {
            this.color = color;
            return this;
        }

        public Builder setYear(int year) {
            this.year = year;
            return this;
        }

        public Car build() {
            return new Car(this);
        }
    }

    private Car(Builder builder) {
        this.model = builder.model;
        this.color = builder.color;
        this.year = builder.year;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getYear() {
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }
}

*****
package com.ecommercearchitect.designpatterns.builder.methodchainingexample;

public class BuilderMethodChainingExampleMain {
    public static void main(String[] args) {
        Car car = new Car.Builder().setModel("Honda Civic").setColor("Red").setYear(2021).build();
        System.out.println(car.getModel()); // output: Honda Civic
        System.out.println(car.getColor()); // output: Red
        System.out.println(car.getYear());  // output: 2021
    }
}

**** OUTPUT ****
Honda Civic
Red
2021

make immutable objects with builder pattern example

To create an immutable object using the Builder Pattern in Java, we need to follow a few steps:

  1. Make the class final: By making the class final, we ensure that no subclass can modify the behavior of the class.

  2. Make the constructor private: By making the constructor private, we ensure that the object can only be created using the Builder.

  3. Create a static inner "Builder" class: This class will contain the methods for setting the values of the object.

  4. Make the Builder class fields final: By making the fields final, we ensure that they cannot be modified once they are set.

  5. Create a method in the Builder class called "build()": This method will create the immutable object and return it.

public final class ImmutableObject {
    private final int value1;
    private final int value2;
    private final String value3;

    private ImmutableObject(int value1, int value2, String value3) {
        this.value1 = value1;
        this.value2 = value2;
        this.value3 = value3;
    }

    public int getValue1() {
        return value1;
    }

    public int getValue2() {
        return value2;
    }

    public String getValue3() {
        return value3;
    }

    public static class Builder {
        private final int value1;
        private final int value2;
        private String value3;

        public Builder(int value1, int value2) {
            this.value1 = value1;
            this.value2 = value2;
        }

        public Builder value3(String value3) {
            this.value3 = value3;
            return this;
        }

        public ImmutableObject build() {
            return new ImmutableObject(value1, value2, value3);
        }
    }
}

**
ImmutableObject immutableObject = new ImmutableObject.Builder(1, 2)
    .value3("Hello, world! I am immutable objec")
    .build();
**

Pros:

Encapsulates object creation: The Builder Pattern encapsulates the construction of the object and the creation of its various parts. This makes the code easier to understand and maintain, and it also makes the object creation process more flexible.

Provides a clear interface: The Builder Pattern provides a clear and concise interface for constructing objects. This makes it easier for developers to use and understand, and it also helps to ensure that the object is constructed correctly.

Allows for incremental construction: The Builder Pattern allows for incremental construction of objects, which means that the object can be constructed in stages. This can be useful when creating complex objects that require many steps to construct.

Enables immutability: The Builder Pattern can be used to create immutable objects, which are objects that cannot be changed once they have been created. This can be useful for creating objects that need to be thread-safe or for creating objects that need to be shared across multiple threads.
Example : StringBuilder class which builds the complex string first and later convert to the string object.

Cons:

Increases complexity: The Builder Pattern can increase the complexity of the code, especially if there are many steps involved in constructing the object. This can make the code harder to understand and maintain.

Requires additional classes: The Builder Pattern requires the creation of additional classes, which can increase the size of the codebase.

Limited flexibility: The Builder Pattern can be less flexible than other creational patterns, such as the Factory Method Pattern. This is because the Builder Pattern is designed to create complex objects that require many steps to construct, whereas the Factory Method Pattern is designed to create objects that can be created in a single step.

Relation with other patterns

  1. Singleton Pattern: The Builder pattern can be used to create a singleton object. Instead of using a traditional singleton pattern, we can use the Builder pattern to create a single instance of an object and ensure that it is immutable.

  2. Factory Method Pattern: The Builder pattern is sometimes used in conjunction with the Factory Method pattern. The Factory Method pattern is used to create objects of a specific type based on a given set of parameters. The Builder pattern can be used to provide those parameters in a flexible way.
    Many designs first start with the Factory Method and then evolve to the Abstract Factory, Prototype or Builder.

  3. Composite Pattern: The Builder pattern can be used in conjunction with the Composite pattern to create complex objects that are made up of smaller objects. The Builder pattern can be used to create the smaller objects and then assemble them into a larger object.

  4. Decorator Pattern: The Builder pattern can be used in conjunction with the Decorator pattern to create objects that have additional functionality added to them. The Builder pattern can be used to create the base object and then the Decorator pattern can be used to add additional functionality to the object.

please #### click here for the complete code on github.

Thanks for reading !!! Love IT Live IT Enjoy IT !!!!

0
Subscribe to my newsletter

Read articles from Aditya Chaudhari directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aditya Chaudhari
Aditya Chaudhari

building efficient, scalable and maintainable enterprise e-commerce applications | Technical Architect | SAP Commerce Cloud (hybris) | Java | Spring | Mirakl