The Builder Pattern is a well-known design pattern in software engineering that allows for the step-by-step construction of complex objects. It’s particularly useful when an object needs to be created in multiple stages or when there are numerous parameters involved. This pattern is a part of the classic Gang of Four (GoF) design patterns and is categorized under the creational patterns. Let’s explain it and see some C# examples.

What is the Builder Pattern?

The Builder Pattern separates the construction of a complex object from its representation, enabling the same construction process to create different representations. This pattern is beneficial when dealing with immutable objects or objects with many optional parameters.

When to Use the Builder Pattern

The Builder Pattern is particularly useful in the following scenarios:

  1. Complex Construction Logic: When the construction of an object is complex and involves multiple steps.
  2. Immutable Objects: When you want to build immutable objects, ensuring that all required fields are provided.
  3. Multiple Representations: When you need different representations of the same construction process.

Components of the Builder Pattern

The Builder Pattern typically involves the following components:

  1. Product: The complex object being built.
  2. Builder: An abstract interface for creating parts of the Product object.
  3. Concrete Builder: A class that implements the Builder interface and constructs and assembles parts of the Product.
  4. Director: Constructs the object using the Builder interface.

Implementing the Builder Pattern in C# by the book

Let’s illustrate the Builder Pattern with a practical example. Suppose we want to build a House object that has multiple attributes such as Doors, Windows, RoofType, and Garage.

Step 1: Define the Product

First, we define the House class, which is the product we want to build.

public class House
{
    public int Doors { get; set; }
    public int Windows { get; set; }
    public string RoofType { get; set; }
    public bool Garage { get; set; }

    public override string ToString()
    {
        return $"House with {Doors} doors, {Windows} windows, {RoofType} roof, Garage: {Garage}";
    }
}

Step 2: Create the Builder Interface

Next, we define the IHouseBuilder interface which specifies the methods for building different parts of the House.

public interface IHouseBuilder
{
    void BuildDoors(int number);
    void BuildWindows(int number);
    void BuildRoof(string type);
    void BuildGarage(bool hasGarage);
    House GetHouse();
}

Step 3: Implement the Concrete Builder

We then implement the IHouseBuilder interface in the ConcreteHouseBuilder class.

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

    public void BuildDoors(int number)
    {
        _house.Doors = number;
    }

    public void BuildWindows(int number)
    {
        _house.Windows = number;
    }

    public void BuildRoof(string type)
    {
        _house.RoofType = type;
    }

    public void BuildGarage(bool hasGarage)
    {
        _house.Garage = hasGarage;
    }

    public House GetHouse()
    {
        return _house;
    }
}

Step 4: Create the Director

The Director class defines the order in which to call the construction steps.

public class HouseDirector
{
    private readonly IHouseBuilder _builder;

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

    public void ConstructHouse()
    {
        _builder.BuildDoors(4);
        _builder.BuildWindows(10);
        _builder.BuildRoof("Gable");
        _builder.BuildGarage(true);
    }

    public House GetHouse()
    {
        return _builder.GetHouse();
    }
}

Step 5: Putting It All Together

Finally, we can use these classes to build a House object.

class Program
{
    static void Main(string[] args)
    {
        IHouseBuilder builder = new ConcreteHouseBuilder();
        HouseDirector director = new HouseDirector(builder);

        director.ConstructHouse();
        House house = director.GetHouse();

        Console.WriteLine(house.ToString());
    }
}

Builder Pattern in a different way

public class BuilderPattern
{
    public static void Example()
    {
        var house = new HouseBuilder(4, 10)
                            .WithRoofType("Gable")
                            .WithGarage(true)
                            .Build();

        Console.WriteLine(house);
    }
}

public class House
{
    public int Doors { get; }
    public int Windows { get; }
    public string RoofType { get; }
    public bool Garage { get; }

    public House(int doors, int windows, string roofType, bool garage)
    {
        Doors = doors;
        Windows = windows;
        RoofType = roofType;
        Garage = garage;
    }

    public override string ToString()
    {
        return $"House with {Doors} doors, {Windows} windows, {RoofType} roof, Garage: {Garage}";
    }
}

public class HouseBuilder
{
    private readonly int _doors;
    private readonly int _windows;

    private string _roofType = "Flat";
    private bool _garage = false;

    public HouseBuilder(int doors, int windows)
    {
        _doors = doors;
        _windows = windows;
    }

    public HouseBuilder WithRoofType(string roofType)
    {
        _roofType = roofType;
        return this;
    }

    public HouseBuilder WithGarage(bool garage)
    {
        _garage = garage;
        return this;
    }

    public House Build()
    {
        return new House(_doors, _windows, _roofType, _garage);
    }
}

Benefits Showcased in This Example

  1. Simplifies Object Creation: The Builder class provides a clear and fluent interface for constructing a House.
  2. Mandatory Parameters: The Builder‘s constructor requires doors and windows, making them mandatory parameters.
  3. Immutable Objects: The House class is immutable because all properties are readonly and set only via the constructor.
  4. Flexibility: Optional parameters such as roofType and garage can be set fluently, allowing different representations of the House.

Conclusion

The Builder Pattern is a powerful design pattern that simplifies the construction of complex objects. By separating the construction logic from the representation, it provides a flexible and reusable approach to object creation. Personally, I like to use this pattern in my unit test projects.

Feel free to experiment with the code examples provided and consider how the Builder Pattern might be applied to your own projects to improve the clarity and robustness of your object construction logic.

Affiliate promo

If you love learning new stuff and want to support me, consider buying a course from Dometrain using this link: Browse courses – Dometrain. Thank you!

Leave a comment

Trending