Builder Pattern in C# .NET

Merwan Chinta
CodeNx
Published in
3 min readJan 28, 2024

--

The Builder pattern is a design pattern used to construct a complex objects step by step, especially when the construction process needs to allow different representations of the object.

Scenarios where Builder Pattern is justified when:

  • Need to construct complex objects.
  • Need granular control over object construction.
  • To enforce Immutable object creation.
  • Constructing different representations.
  • Object has lot of optional parameters/default values.
  • We want more readable/expressive code with help of fluent methods.

In .NET, you can implement the Builder pattern by defining a builder interface, concrete builder classes that implement the interface, a product class, and a director class that constructs the product using the builder interface.

Example of Builder Pattern for constructing a House object

The House can have various features like the number of windows, doors, and a garden.

Builder Pattern in .NET
Builder Pattern in .NET — Image source: Created by Author

Product Class: Represents the complex object under construction.

public class House
{
public int NumberOfWindows { get; set; }
public int NumberOfDoors { get; set; }
public bool HasGarden { get; set; }

public override string ToString()
{
return $"House with {NumberOfWindows} windows, {NumberOfDoors} doors, and " + (HasGarden ? "a garden." : "no garden.");
}
}

Builder Interface: Specifies methods for creating parts of the House object.

public interface IHouseBuilder
{
void BuildWindows(int number);
void BuildDoors(int number);
void BuildGarden(bool hasGarden);

House GetHouse();
}

Concrete Builder Class with Fluent Interface: Implements the builder interface, constructs and assembles parts of the product by implementing the builder interface.

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

public IHouseBuilder BuildWindows(int number)
{
_house.NumberOfWindows = number;
return this; // Return the builder for chaining
}

public IHouseBuilder BuildDoors(int number)
{
_house.NumberOfDoors = number;
return this; // Return the builder for chaining
}

public IHouseBuilder BuildGarden(bool hasGarden)
{
_house.HasGarden = hasGarden;
return this; // Return the builder for chaining
}

public House GetHouse()
{
return _house;
}

public HouseBuilder Reset()
{
_house = new House();
return this; // Return the builder for chaining
}
}

Director Class: Optional class to enforce a particular build process for constructing houses with predefined configurations.

public class HouseDirector
{
private readonly IHouseBuilder _builder;

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

public House ConstructSimpleHouse()
{
return _builder.BuildWindows(4)
.BuildDoors(1)
.BuildGarden(false)
.GetHouse();
}

public House ConstructLuxuryHouse()
{
return _builder.BuildWindows(10)
.BuildDoors(5)
.BuildGarden(true)
.GetHouse();
}
}

Usage with both Director and Direct Builder Access

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

// Using director to construct a simple house
var simpleHouse = director.ConstructSimpleHouse();
Console.WriteLine(simpleHouse);

// Reset the builder before starting a new construction
builder.Reset();

// Using director to construct a luxury house
var luxuryHouse = director.ConstructLuxuryHouse();
Console.WriteLine(luxuryHouse);

// Reset the builder before starting a new construction
builder.Reset();

// Using builder directly for a custom house
var customHouse = builder.BuildWindows(6)
.BuildDoors(2)
.BuildGarden(true)
.GetHouse();

Console.WriteLine(customHouse);
}
}

In this implementation, you have the flexibility to use the Builder directly for more custom or ad-hoc construction sequences, or you can use the Director for more standardized constructions.

The Director encapsulates the construction process, which is beneficial when the construction process is complex or needs to be standardized across different parts of the application.

On the other hand, direct access to the Builder provides more control and customization for the client.

Calling Reset is crucial to ensure that the state from simpleHouse or luxuryHouse does not unintentionally affect the construction of customHouse. If GetHouse handles the reset internally (as in scenario 1), you don't need to call Reset manually.

Potential Downsides

It’s also important to be aware of the potential downsides or situations where the Builder pattern might not be the best choice:

  • Builder pattern can overcomplicate if objects are simple, doesn’t have a lot of optional parameters.
  • In high-critical performance applications, additional layer of abstraction might introduce a small overhead. Though usually its negligible, something to consider for performance-critical apps.
  • It is possible to misuse the builder pattern, that leaves the object in an inconsistent state if not designed carefully.

I trust this information has been valuable to you. 🌟 Wishing you an enjoyable and enriching learning journey!

📚 For more insights like these, feel free to follow 👉 Merwan Chinta

--

--

Merwan Chinta
CodeNx

🚧 Roadblock Eliminator & Learning Advocate 🖥️ Software Architect 🚀 Efficiency & Performance Guide 🌐 Cloud Tech Specialist