TeachingBee

Builder Design Pattern : Low Level System Design

builder design pattern

The Builder Design Pattern is another creational pattern designed to construct a complex object step by step. It’s especially useful when an object needs to be initialized with a number of parameters, some of which may be optional.

Unlike the Factory Pattern, which is used to create different implementations of the same interface, the Builder Pattern is used to create different representations of the same complex object, often by building its various parts.

What is Builder Design Pattern?

The Builder Design Pattern is a creational design pattern that aims to separate the construction of a complex object from its representation. By doing this, the same construction process can create different representations. This pattern is particularly useful when an object needs to be created with a lot of possible configurations, and it’s impractical to have separate constructors for each possible configuration.

Consider a scenario where you’re creating an instance of a User class that has several fields, some of which are mandatory while others are optional.

Class User
    // Declare the fields for the User class. Some are mandatory, others are optional.
    Private firstName As String  // Mandatory
    Private lastName As String   // Mandatory
    Private age As Integer       // Optional
    Private phone As String      // Optional
    Private address As String    // Optional

    // Define the constructor for the User class.
    // This constructor initializes the User object with given values.
    Constructor(firstName As String, lastName As String, age As Integer, phone As String, address As String)
        Set this.firstName to firstName
        Set this.lastName to lastName
        Set this.age to age
        Set this.phone to phone
        Set this.address to address
    EndConstructor

    // Define getters for each field.
    // Example:
    Function GetFirstName() As String
        Return this.firstName
    EndFunction

    // Similarly, define setters if needed and other getters for lastName, age, phone, and address.
EndClass

Instantiating this User class becomes cumbersome and error-prone, especially when dealing with multiple optional parameters. This leads to the so-called “Telescoping constructor” anti-pattern.

Solving the Issue with the Builder Design Pattern

The Builder Design Pattern elegantly solves this problem by allowing step-by-step construction of a complex object and producing different types and representations of an object using the same construction code.

How to Implement Builder Design Pattern?

To implement the Builder Design Pattern, define a separate builder class responsible for constructing complex objects step by step. The builder class contains methods for setting various attributes of the object.

Let’s look this in detail

Components of the Builder Design Pattern

Builder Design Pattern

The components involved in the Builder Design Pattern are:

1. Product Class

The Product class represents the complex object that needs to be constructed. It is often a composite object that consists of various parts. The complexity of the Product class comes from its composition – it might require assembling various components that have to be created in a specific sequence or configuration.

Example:
A Car class could be a Product, with components like engine, wheels, seats, GPS, etc. Each of these components requires detailed construction and setup, and different types of cars might require different configurations of these components.

Class Car
    private Engine engine
    private int numberOfSeats
    private boolean hasGPS

    Method setEngine(engine)
        this.engine = engine

    Method setSeats(numberOfSeats)
        this.numberOfSeats = numberOfSeats

    Method setGPS(hasGPS)
        this.hasGPS = hasGPS
EndClass

2. Builder Interface

The Builder interface declares the step-by-step construction process for building a Product object. It specifies methods for creating the different parts of the Product objects, ensuring that all builders implement a common set of operations required to construct the product.

Example Methods:

  • setEngine(Engine engine): Sets the engine of the car.
  • setSeats(int number): Specifies the number of seats.
  • setGPS(boolean hasGPS): Indicates whether to include GPS.
Interface Builder
    Method setEngine(engine)
    Method setSeats(numberOfSeats)
    Method setGPS(hasGPS)
EndInterface

3. Concrete Builder

Concrete Builders implement the Builder interface and provide specific implementations of the building steps. Each Concrete Builder is responsible for creating and assembling parts of the Product, keeping track of what it builds. Additionally, it often provides a method to retrieve the final product, ensuring that the construction process is abstracted away from the final product itself.

Class CarBuilder Implements Builder
    private Car car

    Constructor()
        this.car = new Car()

    Method setEngine(engine)
        car.setEngine(engine)

    Method setSeats(numberOfSeats)
        car.setSeats(numberOfSeats)

    Method setGPS(hasGPS)
        car.setGPS(hasGPS)

    Method getCar()
        Return this.car
        // Optionally reset the builder
        this.reset()

    Method reset()
        this.car = new Car()
EndClass

Responsibilities:

  • Manage the construction of the product step-by-step.
  • Hold a reference to the product being built.
  • Provide methods to initialize and configure the product’s components.
  • Include a method to deliver the final product, often resetting the builder to be ready to start a new construction.

4. Director (Optional)

The Director class is an optional component that defines the order in which to execute the building steps to construct the products. The Director takes a Builder instance as input and directs the construction process, invoking the building steps defined in the Builder interface. The main purpose of the Director is to encapsulate the construction algorithm from the client, allowing for the creation of complex objects without exposing the construction logic.

Class CarDirector
    Method constructSportsCar(builder)
        builder.reset()
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setGPS(true)
        Return builder.getCar()

    Method constructSUV(builder)
        builder.reset()
        builder.setSeats(8)
        builder.setEngine(new SUVEngine())
        builder.setGPS(true)
        Return builder.getCar()
EndClass

Workflow:

  1. The client creates a Director object and configures it with the desired Builder.
  2. The Director guides the construction process, invoking the necessary building steps on the Builder instance.
  3. Once the construction is complete, the client retrieves the product from the Builder.
// Create a builder object
builder = new CarBuilder()

// Create a director object and use it to construct a car
director = new CarDirector()
sportsCar = director.constructSportsCar(builder)
// Now, sportsCar is a Car object built according to the specifications defined in the constructSportsCar method.

// Similarly, to create an SUV:
suv = director.constructSUV(builder)
// Now, suv is a Car object built according to the specifications defined in the constructSUV method.

Example:
A CarDirector class might dictate the sequence of construction steps for different types of cars, ensuring that each part is built in the correct order and configuration. The Director might have methods like constructSportsCar(Builder builder) or constructSUV(Builder builder) that encapsulate the specific construction sequences for different types of cars.

Builder Design Pattern Example

Identifying Where To Apply Builder Design Pattern

  • Complex Constructors: When your class grows with many constructor parameters, especially optional ones.
  • Immutability: When you need the object to be immutable once it’s fully constructed.
  • Code Readability: When the construction process of an object is complex and requires clarity and readability.
  • Reusable Object Creation: When you need to create multiple variations of an object without duplicating code.
  • Readability Matters: When constructing objects directly in the client code leads to less readable and maintainable code, especially with many optional parameters.

The Builder Pattern provides a flexible and readable solution to constructing complex objects. It’s particularly useful in languages like Java, where the final keyword is used to make immutable objects, ensuring the integrity of the data state.

Common Usage of the Builder Design Pattern

The Builder Design Pattern is commonly used in scenarios where a system needs to create complex objects with multiple parts. It’s particularly beneficial in the following situations:

  1. Immutable Objects: For creating immutable objects without passing a large number of parameters to the constructor or requiring a lot of setter methods.
  2. Configurable Objects: When constructing objects that require various configurations, which might not be known until runtime.
  3. Composite Objects: In composite patterns, where components can be constructed in a step-by-step fashion and the process of constructing an object should be independent of the components that make up the object.
  4. Readability: When initializing an object directly would lead to less readable code, especially when dealing with many optional parameters.
  5. Object Pools: When objects in the pool need to be created with a specific state before being returned to the pool user.

Benefits of the Builder Design Pattern

  1. Improved Readability: The Builder Pattern makes the client code more readable and understandable, especially when dealing with objects that require a lot of parameters for construction.
  2. Immutability: It supports the construction of immutable objects without requiring the object to have a constructor with many parameters.
  3. Flexibility: Allows constructing objects step-by-step, varying the construction process and final representation.
  4. Object Validation: Can centralize the validation logic within the builder, ensuring that the object is in a consistent state before its use.
  5. Fluent Interfaces: Often implemented with fluent interfaces, allowing for chaining methods which further improves readability.

Drawbacks of the Builder Design Pattern

  1. Complexity: Introduces more code and complexity into the codebase, with additional classes and interfaces to maintain.
  2. Overhead: May lead to a performance overhead due to the creation of additional objects (the builders), especially in critical paths of the application.
  3. Misuse: Can be overkill for simple objects, complicating the design unnecessarily.
  4. Initialization Overhead: Requires all parameters to be passed to the builder before the object can be constructed, which might be cumbersome for objects with many attributes.
  5. Maintenance: The builder and the product classes are tightly coupled, meaning any change in the product class structure must be reflected in the builder class.

Key TakeAways

  • Builder pattern separates the construction of complex objects from their representation, allowing for the creation of objects step by step.
  • It provides a flexible approach to building objects with varying configurations, accommodating different requirements without cluttering the constructor.
  • Builders often utilize a fluent interface, enabling a readable and expressive way to specify object construction steps, enhancing code clarity and maintainability.
  • By encapsulating the construction logic within the builder, it abstracts away the details of object creation, promoting clean code architecture and modular design.

Similar Posts

What Is the Difference Between Factory and Abstract Factory Design Patterns?

When to Use Factory Design Pattern in Java?

FAQ

What is the builder pattern in JDK?

What is the @builder function?

What is the rule builder design pattern?

What is an example of builder design pattern in real-time?

90% of Tech Recruiters Judge This In Seconds! 👩‍💻🔍

Don’t let your resume be the weak link. Discover how to make a strong first impression with our free technical resume review!

Related Articles

Types of Memory and Storage in system design thumbnail

Types of Computer Memory and Storage

In this article we will look into different types of computer memory, distinguishing between primary memory and secondary memory. We will also see their characteristics, major kinds, usage, and key

latency in system design thumbnail

What is Latency In System Design?

In this article we will look into Latency In System Design, we will see how is latency introduced into the systems and what are various ways to reduce the latency

Why Aren’t You Getting Interview Calls? 📞❌

It might just be your resume. Let us pinpoint the problem for free and supercharge your job search. 

Newsletter

Don’t miss out! Subscribe now

Log In