Builder Pattern | Creational Design Pattern

Builder 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 a large number of optional parameters, and we want to create an object step by step.

Key components and steps involved in the Builder Pattern:



1. Product Represents the complex object that is being constructed.

2. Builder: Declares the interface for constructing the different parts of the product.

- Provides methods for setting individual components or features.

3. Concrete Builder:- Implements the Builder interface and constructs the final product.

- Keeps track of the constructed parts and offers a method to retrieve the fully constructed product.

4. Director: - Directs the construction process using a builder.

 - Usually, the director is not mandatory, but it can help simplify the client code when constructing the product.

5. Client: Initiates the construction process by using a builder to create the product.


Example in C#:


________
// Step 1: Product
class Product
{
    public string Part1 { get; set; }
    public string Part2 { get; set; }
    public string Part3 { get; set; }

    public void Display()
    {
        Console.WriteLine($"Parts: {Part1}, {Part2}, {Part3}");
    }
}

// Step 2: Builder
interface IBuilder
{
    void BuildPart1();
    void BuildPart2();
    void BuildPart3();
    Product GetResult();
}

// Step 3: Concrete Builder
class ConcreteBuilder : IBuilder
{
    private Product product = new Product();

    public void BuildPart1()
    {
        product.Part1 = "Part 1";
    }

    public void BuildPart2()
    {
        product.Part2 = "Part 2";
    }

    public void BuildPart3()
    {
        product.Part3 = "Part 3";
    }

    public Product GetResult()
    {
        return product;
    }
}

// Step 4: Director (optional)
class Director
{
    private IBuilder builder;

    public Director(IBuilder builder)
    {
        this.builder = builder;
    }

    public void Construct()
    {
        builder.BuildPart1();
        builder.BuildPart2();
        builder.BuildPart3();
    }
}

// Step 5: Client
class Client
{
    static void Main()
    {
        IBuilder builder = new ConcreteBuilder();
        Director director = new Director(builder);

        director.Construct();

        Product product = builder.GetResult();
        product.Display();
    }

________
 
In this example, the ConcreteBuilder implements the IBuilder interface, and the Director directs the construction process. The client initiates the construction and retrieves the final product. The Builder Pattern allows flexibility in constructing different representations of the product by using different concrete builders.



In the context of the Builder Pattern example provided:

1. Complex Object (`Product`):
   - The `Product` class represents the complex object that you want to construct. It usually consists of multiple parts or components. In this example, the `Product` has three parts: `Part1`, `Part2`, and `Part3`.

2. Representation Construction (`ConcreteBuilder`):
   - The `ConcreteBuilder` class implements the `IBuilder` interface and is responsible for constructing the individual parts of the complex object (`Product`). Each method in the `ConcreteBuilder` corresponds to building a specific part of the `Product`.
   
- The construction of the representation involves setting the values of individual components or properties of the `Product`. In this example, constructing the representation involves building `Part1`, `Part2`, and `Part3` in the `ConcreteBuilder`.

The goal is to separate the construction of the `Product` from its representation so that the same construction process can be used to create different representations of the complex object. The `Director` (though optional) can provide a higher-level interface for constructing the `Product`, guiding the sequence of steps required to build the object.

By using the Builder Pattern, we can have different `ConcreteBuilder` implementations that produce variations of the `Product` without modifying the client code. The client can choose the specific `ConcreteBuilder` it wants to use, and the construction process remains consistent.



Real-world example where the Builder Pattern is commonly used is in the construction of objects with a significant number of optional parameters, such as building complex configurations for objects. 

Let's consider a scenario involving the creation of a document using a builder.


Real-world Example: Document Construction


Suppose we're working on a document editor, and you want to construct documents with various formatting options. The document might have a title, paragraphs, images, fonts, and colors. Here's how the Builder Pattern could be applied:


1. Product (`Document`):

   - Represents the complex object being constructed, i.e., the document.


2. Builder (`DocumentBuilder`):

   - Declares the interface for building different parts of the document.

   - Provides methods for setting optional features, like title, paragraphs, fonts, colors, etc.


3. Concrete Builder (`RichDocumentBuilder`):

   - Implements the `DocumentBuilder` interface.

   - Constructs and assembles the `Document` by setting various optional features.


4. Director (`DocumentDirector`):

   - Optionally defines a higher-level interface for constructing the document. It orchestrates the construction process using a builder.


5. Client:

   - Initiates the document construction process using the builder.


Example in C#:

// Step 1: Product
class Document
{
    public string Title { get; set; }
    public List<string> Paragraphs { get; set; }
    public string Font { get; set; }
    public string Color { get; set; }

    // Additional properties...

    public void Display()
    {
        Console.WriteLine($"Title: {Title}");
        Console.WriteLine("Paragraphs:");
        foreach (var paragraph in Paragraphs)
        {
            Console.WriteLine($"- {paragraph}");
        }
        Console.WriteLine($"Font: {Font}, Color: {Color}");
        // Display other properties...
    }
}

// Step 2: Builder
interface IDocumentBuilder
{
    void BuildTitle(string title);
    void BuildParagraph(string paragraph);
    void BuildFont(string font);
    void BuildColor(string color);

    // Additional methods for other properties...
    
    Document GetResult();
}

// Step 3: Concrete Builder
class RichDocumentBuilder : IDocumentBuilder
{
    private Document document = new Document();

    public void BuildTitle(string title)
    {
        document.Title = title;
    }

    public void BuildParagraph(string paragraph)
    {
        if (document.Paragraphs == null)
            document.Paragraphs = new List<string>();
        document.Paragraphs.Add(paragraph);
    }

    public void BuildFont(string font)
    {
        document.Font = font;
    }

    public void BuildColor(string color)
    {
        document.Color = color;
    }

    public Document GetResult()
    {
        return document;
    }
}

// Step 4: Director (optional)
class DocumentDirector
{
    private IDocumentBuilder builder;

    public DocumentDirector(IDocumentBuilder builder)
    {
        this.builder = builder;
    }

    public void ConstructDocument()
    {
        builder.BuildTitle("Sample Document");
        builder.BuildParagraph("Introduction");
        builder.BuildParagraph("Body");
        builder.BuildFont("Arial");
        builder.BuildColor("Black");
    }
}

// Step 5: Client
class Client
{
    static void Main()
    {
        IDocumentBuilder builder = new RichDocumentBuilder();
        DocumentDirector director = new DocumentDirector(builder);

        // Constructing the document
        director.ConstructDocument();

        // Retrieving the final document
        Document document = builder.GetResult();

        // Displaying the document
        document.Display();
    }
}

In this example, the Document is the complex object being constructed. The RichDocumentBuilder is a concrete builder implementing IDocumentBuilder, and the DocumentDirector orchestrates the construction process. The client initiates the document construction using the builder. This way, you can easily create different types of documents by using different builders or customizing the construction process





Real-world Example: Car Construction

1. Product (`Car`):
   - Represents the complex object being constructed, i.e., the car.

2. Builder (`CarBuilder`):
   - Declares the interface for building different parts of the car.
   - Provides methods for setting optional features, like model, color, engine type, and accessories.

3. Concrete Builder (`LuxuryCarBuilder`):
   - Implements the `CarBuilder` interface.
   - Constructs and assembles the `Car` by setting various optional features.

4. Director (`CarDirector`):
   - Optionally defines a higher-level interface for constructing the car. It orchestrates the construction process using a builder.

5. Client:
   - Initiates the car construction process using the builder.

Here's a simplified example in C#:


// Step 1: Product
class Car
{
    public string Model { get; set; }
    public string Color { get; set; }
    public string EngineType { get; set; }
    public bool GPS { get; set; }
    public bool Sunroof { get; set; }

    public void Display()
    {
        Console.WriteLine($"Model: {Model}");
        Console.WriteLine($"Color: {Color}");
        Console.WriteLine($"Engine Type: {EngineType}");
        Console.WriteLine($"GPS: {(GPS ? "Yes" : "No")}");
        Console.WriteLine($"Sunroof: {(Sunroof ? "Yes" : "No")}");
    }
}

// Step 2: Builder
interface ICarBuilder
{
    void BuildModel(string model);
    void BuildColor(string color);
    void BuildEngineType(string engineType);
    void AddGPS();
    void AddSunroof();

    Car GetResult();
}

// Step 3: Concrete Builder
class LuxuryCarBuilder : ICarBuilder
{
    private Car car = new Car();

    public void BuildModel(string model)
    {
        car.Model = model;
    }

    public void BuildColor(string color)
    {
        car.Color = color;
    }

    public void BuildEngineType(string engineType)
    {
        car.EngineType = engineType;
    }

    public void AddGPS()
    {
        car.GPS = true;
    }

    public void AddSunroof()
    {
        car.Sunroof = true;
    }

    public Car GetResult()
    {
        return car;
    }
}

// Step 4: Director (optional)
class CarDirector
{
    private ICarBuilder builder;

    public CarDirector(ICarBuilder builder)
    {
        this.builder = builder;
    }

    public void ConstructCar()
    {
        builder.BuildModel("Luxury Model");
        builder.BuildColor("Silver");
        builder.BuildEngineType("V8");
        builder.AddGPS();
        builder.AddSunroof();
    }
}

// Step 5: Client
class Client
{
    static void Main()
    {
        ICarBuilder builder = new LuxuryCarBuilder();
        CarDirector director = new CarDirector(builder);

        // Constructing the car
        director.ConstructCar();

        // Retrieving the final car
        Car car = builder.GetResult();

        // Displaying the car
        car.Display();
    }
}


In this example, the `Car` is the complex object being constructed. The `LuxuryCarBuilder` is a concrete builder implementing `ICarBuilder`, and the `CarDirector` orchestrates the construction process. The client initiates the car construction using the builder. This way, you can easily create different types of cars by using different builders or customizing the construction process.


We could also create a `Car` class with a constructor and set properties directly. However, the Builder Pattern becomes more beneficial in scenarios where:

1. Complex Construction Logic:
   - If constructing an object involves complex logic, especially when there are multiple steps or optional features, the Builder Pattern provides a clean separation of concerns.

2. Variability in Object Construction:
   - When you need to create multiple variations of an object with different configurations, the Builder Pattern allows you to reuse the construction process by using different builders.

3. Readability and Maintainability:
   - When you want to improve the readability of client code and make it more maintainable, especially when dealing with a large number of parameters or optional features.

4. Avoiding Telescoping Constructors:
   - As the number of parameters increases, using a telescoping constructor pattern (constructor overloading with different parameter combinations) can become unwieldy. The Builder Pattern avoids this issue.

5. Flexibility for Future Changes:
   - If there is a possibility that the construction process or the object's structure may change in the future, the Builder Pattern provides a flexible way to adapt without modifying the client code.

Let's consider the example of the `LuxuryCarBuilder`. If, in the future, we decide to add more optional features or change the way certain features are constructed, the client code using the builder remains unaffected. We can introduce new concrete builders or modify existing ones without impacting the client code.

In simpler scenarios with a few parameters, direct construction may be sufficient. However, as the complexity and variability in object construction increase, the Builder Pattern provides a more structured and maintainable solution.


We can certainly incorporate dynamic logic, such as checking the current time, inside the `AddSunroof` method of the builder. This allows the builder to make decisions based on dynamic conditions during the construction process.

Here's how we could modify the `LuxuryCarBuilder` to include this logic:


public class LuxuryCarBuilder : ICarBuilder
{
    private Car car = new Car();

    public void BuildModel(string model)
    {
        car.Model = model;
    }

    public void BuildColor(string color)
    {
        car.Color = color;
    }

    public void BuildEngineType(string engineType)
    {
        car.EngineType = engineType;
    }

    public void AddGPS()
    {
        car.GPS = true;
    }

    public void AddSunroof()
    {
        // Dynamic logic inside AddSunroof
        if (DateTime.Now.Hour > 12)
        {
            car.Sunroof = true;
        }
    }

    public Car GetResult()
    {
        return car;
    }
}


With this modification, the `AddSunroof` method now checks the current time, and if it's past noon, it adds a sunroof to the car. This way, the client doesn't need to explicitly handle this logic; it is encapsulated within the builder.

Now, when the client calls `builder.AddSunroof()`, the builder internally checks the time and decides whether to add a sunroof based on the dynamic condition.


Comments