Builder Pattern in C# - Building Complex Objects Flexibly
Learn about Builder Pattern - a Creational design pattern that helps build complex objects step by step, avoids massive constructors, and creates different variations of the same object.
Table of Contents
No table of contents available for this article
1. Introduction to Builder Pattern
Builder is a design pattern belonging to the Creational Pattern group - design patterns that focus on optimal and flexible object initialization.
Builder Pattern provides an elegant solution to the problem of creating complex objects in object-oriented programming. Instead of dealing with "dinosaur" constructors containing dozens of parameters, Builder allows you to:
✅ Build complex objects from simple components
✅ Build step by step in a controlled manner
✅ Create different variations of the same object just by changing the construction process
2. Purpose and Origin
Problem - The Challenge to Solve
Imagine you're building a real estate management application. You need to create a House object with many attributes:
// "Dinosaur" constructor - Nightmare!
public House(int walls, int doors, int windows, string roofType,
bool hasGarage, bool hasGarden, bool hasSwimmingPool,
bool hasHeatingSystem, bool hasAirConditioning, ...)What problems will occur?
Problem 1: Constructor too long and hard to use
// Can you remember the parameter order?
var house = new House(4, 2, 6, "tile", true, false, false, true, false, ...);
// What's true? What's false? 😵Problem 2: Subclass explosion If you try to solve this by creating many subclasses like SimpleHouse, VillaHouse, MansionHouse, VillaWithPoolHouse, MansionWithGarageAndPoolHouse... you'll end up with hundreds of subclasses!
Problem 3: Many unused parameters
// Most houses don't have swimming pools
var normalHouse = new House(4, 2, 6, "tile", false, true,
false, // Swimming pool = false
true, false);Solution - The Builder Pattern Approach
Builder Pattern proposes a smart approach:
Step 1: Extract construction logic from the main class Instead of cramming all logic into the constructor, we create a separate Builder class.
Step 2: Build step by step
var house = new HouseBuilder()
.SetWalls(4)
.SetDoors(2)
.SetWindows(6)
.SetRoof("tile")
.AddGarage() // Only add when needed
.AddGarden() // Flexible!
.Build();Step 3: Create multiple Builders for different variations
Just like in real construction, you can have:
WoodHouseBuilder: Build houses with wood and glass
StoneHouseBuilder: Build castles with stone and iron
LuxuryHouseBuilder: Build mansions with gold and diamonds 💎
Same set of construction steps (walls, doors, windows...) but produces different products!
Director - The Orchestrator
For even more convenience, you can create a Director class to manage common "construction recipes":
public class HouseDirector
{
public House BuildSimpleHouse(IHouseBuilder builder)
{
return builder
.SetWalls(4)
.SetDoors(1)
.SetWindows(2)
.SetRoof("simple")
.Build();
}
public House BuildLuxuryVilla(IHouseBuilder builder)
{
return builder
.SetWalls(10)
.SetDoors(3)
.SetWindows(20)
.SetRoof("luxury-tile")
.AddGarage()
.AddGarden()
.AddSwimmingPool()
.Build();
}
}4. Builder Pattern Architecture
Main Components
1. Builder Interface
- Defines basic construction methods
- Common interface for all Concrete Builders
2. Concrete Builder
- Implements specific construction steps
- Each Concrete Builder can produce different products
- Example: WoodHouseBuilder, StoneHouseBuilder
3. Product
- The final object created
- Products from different Builders can be completely different in structure
4. Director (Optional)
- Defines the order of construction steps
- Contains reusable construction "recipes"
- Separates construction logic from client code
5. Client
- Initializes Builder
- Can use Director or call construction steps directly
- Receives final product from Builder
#### Operation Diagram
```
Client -> Director -> Builder Interface -> Concrete Builder -> Product
Real-world example:
Client wants a
Carand an instructionManualDirector doesn't directly create Car or Manual
Director only calls methods in Builder Interface
CarBuilderimplements interface to create CarManualBuilderimplements interface to create ManualResult: Same process but creates 2 completely different products!
4. Advantages & Disadvantages
✅ Advantages
1. Step-by-step construction
Good control over initialization process
Can defer execution of some steps
Can call steps recursively (useful for tree structures)
2. Code reusability
Same construction code can create many product variations
Reduces code duplication
3. Single Responsibility Principle
Separates complex construction logic from Business Logic
Code easier to maintain and extend
4. High flexibility
Easy to change product representation
Adding new Builders doesn't affect existing code
5. Avoids "Telescoping Constructor"
// Before Builder - Hard to read!
new House(4, 2, 6, "tile", true, false, false, true, false);
// After Builder - Clear!
new HouseBuilder()
.SetWalls(4)
.SetDoors(2)
.AddGarage()
.Build();❌ Disadvantages
1. Increases code complexity
Must create many new classes
For simple objects, Builder might be overkill
2. Must create separate Builder for each product type
If you have 10 different product types → need 10 Builders
Costly to maintain
3. Builder class must be mutable
Construction methods change internal state
Not suitable for immutable design
5. When to Use Builder Pattern
Case 1: Avoid "Telescoping Constructor"
// ❌ Hard to understand
public Pizza(string size, bool cheese, bool pepperoni, bool bacon,
bool mushrooms, bool olives, bool onions, ...)
// ✅ Easy to understand
var pizza = new PizzaBuilder()
.SetSize("large")
.AddCheese()
.AddPepperoni()
.AddMushrooms()
.Build();Case 2: Create many variations of the same product
csharp
// Both are pizzas but...
var veggiePizza = new PizzaBuilder()
.AddMushrooms()
.AddOlives()
.AddOnions()
.Build();
var meatLoversPizza = new PizzaBuilder()
.AddPepperoni()
.AddBacon()
.AddSausage()
.Build();Case 3: Build complex structures (Composite Tree)
csharp
// Building folder tree
var fileSystem = new FileSystemBuilder()
.AddFolder("Documents")
.AddFile("report.pdf")
.AddFile("data.xlsx")
.ExitFolder()
.AddFolder("Photos")
.AddFile("vacation.jpg")
.ExitFolder()
.Build();Builder is very useful because:
Builds step by step, doesn't expose incomplete product
Can defer some steps
Can call recursively (important for tree structures)
6. C# Source Code Example
// Builder Interface: Defines construction steps
public interface IBuilder
{
void BuildPartA();
void BuildPartB();
void BuildPartC();
}
// Concrete Builder: Implements specific steps
public class ConcreteBuilder : IBuilder
{
private Product _product = new Product();
public ConcreteBuilder()
{
this.Reset();
}
public void Reset()
{
this._product = new Product();
}
public void BuildPartA()
{
this._product.Add("PartA1");
}
public void BuildPartB()
{
this._product.Add("PartB1");
}
public void BuildPartC()
{
this._product.Add("PartC1");
}
public Product GetProduct()
{
Product result = this._product;
this.Reset();
return result;
}
}
// Product: Final product
// Note: Different Concrete Builders can create
// completely different and unrelated Products
public class Product
{
private List<object> _parts = new List<object>();
public void Add(string part)
{
this._parts.Add(part);
}
public string ListParts()
{
string str = string.Empty;
for (int i = 0; i < this._parts.Count; i++)
{
str += this._parts[i] + ", ";
}
str = str.Remove(str.Length - 2);
return "Product parts: " + str + "\n";
}
}
// Director: Manages construction order
// Director class is optional - Client can control directly
public class Director
{
private IBuilder _builder;
public IBuilder Builder
{
set { _builder = value; }
}
// Build basic version
public void BuildMinimalViableProduct()
{
this._builder.BuildPartA();
}
// Build full-featured version
public void BuildFullFeaturedProduct()
{
this._builder.BuildPartA();
this._builder.BuildPartB();
this._builder.BuildPartC();
}
}
class Program
{
// Client code: Create builder, pass to Director, and receive result
static void Main(string[] args)
{
var director = new Director();
var builder = new ConcreteBuilder();
director.Builder = builder;
Console.WriteLine("Standard basic product:");
director.BuildMinimalViableProduct();
Console.WriteLine(builder.GetProduct().ListParts());
Console.WriteLine("Standard full featured product:");
director.BuildFullFeaturedProduct();
Console.WriteLine(builder.GetProduct().ListParts());
// Client can also control Builder directly
Console.WriteLine("Custom product:");
builder.BuildPartA();
builder.BuildPartC();
Console.Write(builder.GetProduct().ListParts());
}
}
Standard basic product:
Product parts: PartA1
Standard full featured product:
Product parts: PartA1, PartB1, PartC1
Custom product:
Product parts: PartA1, PartC17. Related Design Patterns
Composite Pattern
Provides a way to represent hierarchical tree structures
Builder is often used to construct Composite trees
Iterator Pattern
Traverses elements of an object structure
Can be combined with Builder to process each part
Visitor Pattern
Defines new operations on elements of a structure
Can be applied to Products created by Builder
Interpreter Pattern
Represents grammar as tree structure (Abstract Syntax Tree)
Builder can be used to construct syntax trees
Summary
Builder Pattern is a powerful tool that helps you:
✅ Build complex objects in a controlled manner
✅ Avoid massive, confusing constructors
✅ Create product variations flexibly
✅ Separate construction logic from business logic
Use Builder when your object has many optional attributes or needs to be built in ordered steps!
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns