Builder Design Pattern

Builder Design Pattern

23 Sep 2024
Intermediate
173K Views
27 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Builder Design Pattern

Builder Design Pattern is an important design pattern in C#, Java, Python, and many other languages that allows you to develop complex objects step by step. It lets you build a complicated item by separating the creation process from the representation, allowing you to generate several representations of the same type of object. This pattern gives a straightforward way to build an object, especially when the process involves multiple steps or distinct configurations.

In this design pattern tutorial, we will look at the Builder Design Pattern, explaining what it is, when to use it, and how to implement it using examples. We will begin by studying "What is the Builder Design Pattern?"

What is a Builder Pattern?

  • The Builder pattern is an object-oriented design pattern used to build complex objects step by step.
  • It differentiates between the construction process and the representation, allowing the same construction process to generate many representations.
  • A builder class typically has methods for configuring the various components of the product and a final Build function that creates the fully built item.

When to use the builder pattern?

The Builder pattern is applied when:
  • Complex Object Construction: When an object requires sophisticated initialization or building that includes numerous processes or configurations.
  • Multiple Representations: When a single construction process must produce multiple representations or configurations of an entity.
  • Immutability: When you want to make immutable objects with different settings.
  • Code Readability and Maintainability: Encapsulate the building logic and separate it from the object representation to improve code readability and maintainability.

Builder Pattern - UML Diagram & Implementation

The UML class diagram for the implementation of the builder design pattern is given below:

The classes, interfaces, and objects in the above UML class diagram are as follows:

1. Builder

  • Role: Defines the abstract interface for manufacturing product pieces.
  • Responsibilities: Provide methods for building each component of the product and, in most cases, a way to retrieve the finished result.
  • Example

     public interface IHouseBuilder
    {
        void BuildWalls();
        void BuildRoof();
        void BuildDoors();
        void BuildWindows();
        House GetResult();
    }  

Explanation

The ConcreteHouseBuilder class implements the IHouseBuilder interface and creates a House object with specific properties, such as brick walls, a tile roof, wooden doors, and glass windows. It has methods for building each component of the house and a GetResult method that returns the final House object.

2. ConcreteBuilder

  • Role: Implements the Builder interface for creating and assembling Product components.
  • Responsibilities: Contains the precise implementation for each construction phase and the finished result.

Example

public class ConcreteHouseBuilder : IHouseBuilder
{
    private House _house = new House();

    public void BuildWalls() { _house.Walls = "Brick"; }
    public void BuildRoof() { _house.Roof = "Tile"; }
    public void BuildDoors() { _house.Doors = "Wooden"; }
    public void BuildWindows() { _house.Windows = "Glass"; }

    public House GetResult() { return _house; }
}

Explanation

The ConcreteHouseBuilder class, which implements the IHouseBuilder interface, creates a house object with brick walls, a tile roof, wooden doors, and glass windows. It also includes a method for returning the completed house.

3. Product

  • Role: The complex thing under construction.
  • Responsibilities: Represents the end product of the creation process, which is often a class with numerous attributes that may be configured using the builder.

Example

public class House
{
    public string Walls { get; set; }
    public string Roof { get; set; }
    public string Doors { get; set; }
    public string Windows { get; set; }

    public override string ToString()
    {
        return $"House with {Walls} walls, {Roof} roof, {Doors} doors, and {Windows} windows.";
    }
}

Explanation

The House class represents a house and has attributes for walls, roofs, doors, and windows that are described as strings. It overrides the ToString method and returns a descriptive string of the house's properties.

4. Director

Role: Use a Builder instance to manage the construction process.
Responsibilities: This section defines the order in which building stages are carried out to ensure that the product is created appropriately.

Example

public class HouseDirector
{
    private IHouseBuilder _builder;

    public HouseDirector(IHouseBuilder builder)
    {
        _builder = builder;
    }

    public void ConstructHouse()
    {
        _builder.BuildWalls();
        _builder.BuildRoof();
        _builder.BuildDoors();
        _builder.BuildWindows();
    }
}

Explanation

The HouseDirector class manages the construction of a House by utilizing an IHouseBuilder instance. It describes the sequence of building processes in its ConstructHouse technique to ensure that the house is created in the proper order. This distinction enables different builders to be employed interchangeably, resulting in diverse styles of homes.

How Does the Builder Pattern Work?

Consider the following example of how the Builder pattern works: We need to create a House object. A house can have various features, such as walls, roofs, doors, and windows. Building a house can be a difficult process; therefore, utilizing a Builder pattern might assist in managing the complexity.

Example


public class House
{
    public string Walls { get; set; }
    public string Roof { get; set; }
    public string Doors { get; set; }
    public string Windows { get; set; }

    public override string ToString()
    {
        return $"House with {Walls} walls, {Roof} roof, {Doors} doors, and {Windows} windows.";
    }
}

public interface IHouseBuilder
{
    void BuildWalls();
    void BuildRoof();
    void BuildDoors();
    void BuildWindows();
    House GetResult();
}

public class ConcreteHouseBuilder : IHouseBuilder
{
    private House _house = new House();

    public void BuildWalls() { _house.Walls = "Brick"; }
    public void BuildRoof() { _house.Roof = "Tile"; }
    public void BuildDoors() { _house.Doors = "Wooden"; }
    public void BuildWindows() { _house.Windows = "Glass"; }

    public House GetResult() { return _house; }
}

public class HouseDirector
{
    private IHouseBuilder _builder;

    public HouseDirector(IHouseBuilder builder)
    {
        _builder = builder;
    }

    public void ConstructHouse()
    {
        _builder.BuildWalls();
        _builder.BuildRoof();
        _builder.BuildDoors();
        _builder.BuildWindows();
    }
}

public class Program
{
    public static void Main()
    {
        IHouseBuilder builder = new ConcreteHouseBuilder();
        HouseDirector director = new HouseDirector(builder);

        director.ConstructHouse();
        House house = builder.GetResult();

        System.Console.WriteLine(house);
    }
}
        

class House:
    def __init__(self):
        self.walls = None
        self.roof = None
        self.doors = None
        self.windows = None

    def __str__(self):
        return f"House with {self.walls} walls, {self.roof} roof, {self.doors} doors, and {self.windows} windows."


class HouseBuilder:
    def build_walls(self): pass
    def build_roof(self): pass
    def build_doors(self): pass
    def build_windows(self): pass
    def get_result(self): pass


class ConcreteHouseBuilder(HouseBuilder):
    def __init__(self):
        self.house = House()

    def build_walls(self):
        self.house.walls = "Brick"

    def build_roof(self):
        self.house.roof = "Tile"

    def build_doors(self):
        self.house.doors = "Wooden"

    def build_windows(self):
        self.house.windows = "Glass"

    def get_result(self):
        return self.house


class HouseDirector:
    def __init__(self, builder):
        self.builder = builder

    def construct_house(self):
        self.builder.build_walls()
        self.builder.build_roof()
        self.builder.build_doors()
        self.builder.build_windows()


if __name__ == "__main__":
    builder = ConcreteHouseBuilder()
    director = HouseDirector(builder)
    director.construct_house()
    house = builder.get_result()
    print(house)
        

class House {
    public String walls;
    public String roof;
    public String doors;
    public String windows;

    @Override
    public String toString() {
        return "House with " + walls + " walls, " + roof + " roof, " + doors + " doors, and " + windows + " windows.";
    }
}

interface HouseBuilder {
    void buildWalls();
    void buildRoof();
    void buildDoors();
    void buildWindows();
    House getResult();
}

class ConcreteHouseBuilder implements HouseBuilder {
    private House house = new House();

    public void buildWalls() { house.walls = "Brick"; }
    public void buildRoof() { house.roof = "Tile"; }
    public void buildDoors() { house.doors = "Wooden"; }
    public void buildWindows() { house.windows = "Glass"; }

    public House getResult() { return house; }
}

class HouseDirector {
    private HouseBuilder builder;

    public HouseDirector(HouseBuilder builder) {
        this.builder = builder;
    }

    public void constructHouse() {
        builder.buildWalls();
        builder.buildRoof();
        builder.buildDoors();
        builder.buildWindows();
    }
}

public class Main {
    public static void main(String[] args) {
        HouseBuilder builder = new ConcreteHouseBuilder();
        HouseDirector director = new HouseDirector(builder);
        director.constructHouse();
        House house = builder.getResult();
        System.out.println(house);
    }
}
        

class House {
    constructor() {
        this.walls = null;
        this.roof = null;
        this.doors = null;
        this.windows = null;
    }

    toString() {
        return `House with ${this.walls} walls, ${this.roof} roof, ${this.doors} doors, and ${this.windows} windows.`;
    }
}

class HouseBuilder {
    buildWalls() {}
    buildRoof() {}
    buildDoors() {}
    buildWindows() {}
    getResult() {}
}

class ConcreteHouseBuilder extends HouseBuilder {
    constructor() {
        super();
        this.house = new House();
    }

    buildWalls() { this.house.walls = "Brick"; }
    buildRoof() { this.house.roof = "Tile"; }
    buildDoors() { this.house.doors = "Wooden"; }
    buildWindows() { this.house.windows = "Glass"; }
    getResult() { return this.house; }
}

class HouseDirector {
    constructor(builder) {
        this.builder = builder;
    }

    constructHouse() {
        this.builder.buildWalls();
        this.builder.buildRoof();
        this.builder.buildDoors();
        this.builder.buildWindows();
    }
}

const builder = new ConcreteHouseBuilder();
const director = new HouseDirector(builder);
director.constructHouse();
const house = builder.getResult();
console.log(house.toString());
        

// Product
class House {
    walls: string | null = null;
    roof: string | null = null;
    doors: string | null = null;
    windows: string | null = null;

    toString(): string {
        return `House with ${this.walls} walls, ${this.roof} roof, ${this.doors} doors, and ${this.windows} windows.`;
    }
}

// Builder Interface
interface IHouseBuilder {
    buildWalls(): void;
    buildRoof(): void;
    buildDoors(): void;
    buildWindows(): void;
    getResult(): House;
}

// Concrete Builder
class ConcreteHouseBuilder implements IHouseBuilder {
    private house: House = new House();

    buildWalls(): void {
        this.house.walls = "Brick";
    }

    buildRoof(): void {
        this.house.roof = "Tile";
    }

    buildDoors(): void {
        this.house.doors = "Wooden";
    }

    buildWindows(): void {
        this.house.windows = "Glass";
    }

    getResult(): House {
        return this.house;
    }
}

// Director
class HouseDirector {
    private builder: IHouseBuilder;

    constructor(builder: IHouseBuilder) {
        this.builder = builder;
    }

    constructHouse(): void {
        this.builder.buildWalls();
        this.builder.buildRoof();
        this.builder.buildDoors();
        this.builder.buildWindows();
    }
}

// Usage
const builder: IHouseBuilder = new ConcreteHouseBuilder();
const director: HouseDirector = new HouseDirector(builder);

director.constructHouse();
const house: House = builder.getResult();

console.log(house.toString());
        

Explanation

This example uses the Builder pattern, with House as the product class, IHouseBuilder as the builder interface, ConcreteHouseBuilder as the concrete builder that implements the construction phases, and HouseDirector directing the building process. The Program class builds a house and prints its description.

Output

House with Brick walls, Tile roof, Wooden doors, and Glass windows.

Advantages of the Builder Pattern:

  • Separation of Concerns: Separates the building logic of complex objects from their representation.
  • Flexible construction: Allows for multiple representations of a product using the same method.
  • Readability and Maintenance: Improves code readability and maintainability by encapsulating the construction process.
Summary
The C# Builder Design Pattern separates the creation process from the representation, allowing for the step-by-step construction of complicated objects. This pattern configures components using a builder class and manages the creation sequence with a director, allowing for different representations of the same object. Encapsulating the building logic increases code readability, maintainability, and adaptability. Also, consider our .NET design patterns training for a better understanding of other design patterns concepts.

FAQs

There are no new objects created (basic function of the builder pattern) and there is no design pattern involved.

Suppose the restaurant decides to offer more types of meals in the future (e.g., “KetoMeal”, “VegetarianMeal”, etc.)

The main difference between them is that the Builder pattern primarily describes the creation of complex objects step by step. In the Abstract Factory pattern, the emphasis is on families of objects-products. Builder returns the product in the last step
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan is the Founder and CEO at ScholarHat by DotNetTricks which is a brand when it comes to e-Learning. He provides training and consultation over an array of technologies like Cloud, .NET, Angular, React, Node, Microservices, Containers and Mobile Apps development. He has been awarded Microsoft MVP 9th time in a row (2016-2024). He has changed many lives with his writings and unique training programs. He has a number of most sought-after books to his name which has helped job aspirants in cracking tough interviews with ease.
Accept cookies & close this