Fluent Builder Pattern: How to Simplify Complex Object Creation

Oct 08 2024

 
 

The Background

 
 

When building complex objects in C#, constructors can quickly become unwieldy, especially when dealing with optional parameters.
The Fluent Builder Pattern offers an elegant solution, allowing developers to simplify object construction through method chaining while maintaining readability and flexibility.
In this article, we’ll dive deep into the Fluent Builder Pattern, explain its benefits, and explore advanced techniques that make this pattern an essential tool for modern C# development.

   

The Problem with Complex Constructors

   

Consider the following scenario: you are tasked with creating a Car class that has multiple properties like Make, Model, Year, and Color. A straightforward approach would involve constructing the object using a constructor like this:

public class Car
{
    public string Make { get; }
    public string Model { get; }
    public int Year { get; }
    public string Color { get; }

    public Car(string make, string model, int year, string color)
    {
        Make = make;
        Model = model;
        Year = year;
        Color = color;
    }
}
As more properties are added or some properties become optional, the constructor’s parameter list grows longer, making it difficult to manage. This leads to code that is hard to read, maintain, and extend over time.

 

Enter the Fluent Builder Pattern.

   

What is the Fluent Builder Pattern?

   

The Fluent Builder Pattern is an extension of the Builder Pattern that emphasizes readability and flexibility. It involves chaining method calls that set properties on an object being built. Instead of using long constructors, each property is configured using a method, and the builder returns itself, allowing for chained calls.

 

Why use the Fluent Builder Pattern?

 

• Readable Object Construction: Each method call explicitly sets a property, improving readability.
• Optional Parameters: You can provide default values and avoid cluttering constructors with unnecessary parameters.
• Modularity: Builders can be easily modified and extended to support new requirements without changing the object’s interface.

   

Basic Implementation of Fluent Builder Pattern

   

Let’s look at a basic implementation of the Fluent Builder pattern to construct a Car object.

public class Car
{
    public string Make { get; set; }
    public string Model { get; set; }
    public int Year { get; set; }
    public string Color { get; set; }

    public override string ToString()
    {
        return $"{Year} {Color} {Make} {Model}";
    }
}

public class CarBuilder
{
    private readonly Car _car = new Car();

    public CarBuilder WithMake(string make)
    {
        _car.Make = make;
        return this;
    }

    public CarBuilder WithModel(string model)
    {
        _car.Model = model;
        return this;
    }

    public CarBuilder WithYear(int year)
    {
        _car.Year = year;
        return this;
    }

    public CarBuilder WithColor(string color)
    {
        _car.Color = color;
        return this;
    }

    public Car Build()
    {
        return _car;
    }
}

 

Now, you can easily create a car with the following syntax:

Car car = new CarBuilder()
                .WithMake("Tesla")
                .WithModel("Model S")
                .WithYear(2023)
                .WithColor("White")
                .Build();
                
Console.WriteLine(car);  // Output: 2023 White Tesla Model S

   

Fluent Builders for Complex Hierarchies

   

In real-world scenarios, you might need to build objects that contain other complex objects. For example, a Car might have an Engine object. You can use nested builders to handle such scenarios:

public class Engine
{
    public int HorsePower { get; set; }
    public string Type { get; set; }
}

public class Car
{
    public string Make { get; set; }
    public string Model { get; set; }
    public Engine Engine { get; set; }
}

public class EngineBuilder
{
    private readonly Engine _engine = new Engine();

    public EngineBuilder WithHorsePower(int hp)
    {
        _engine.HorsePower = hp;
        return this;
    }

    public EngineBuilder WithType(string type)
    {
        _engine.Type = type;
        return this;
    }

    public Engine Build()
    {
        return _engine;
    }
}

public class CarBuilder
{
    private readonly Car _car = new Car();
    private readonly EngineBuilder _engineBuilder = new EngineBuilder();

    public CarBuilder WithMake(string make)
    {
        _car.Make = make;
        return this;
    }

    public CarBuilder WithModel(string model)
    {
        _car.Model = model;
        return this;
    }

    public EngineBuilder WithEngine()
    {
        return _engineBuilder;
    }

    public Car Build()
    {
        _car.Engine = _engineBuilder.Build();
        return _car;
    }
}

 

*Now you can build complex hierarchical objects using nested builders:

Car car = new CarBuilder()
                .WithMake("Ford")
                .WithModel("Mustang")
                .WithEngine()
                    .WithHorsePower(450)
                    .WithType("V8")
                .Build();

Console.WriteLine(car);  // Output: Ford Mustang with 450 HP V8 engine

   

Supporting Immutable Objects

   

Sometimes, you want to ensure that once an object is created, it cannot be modified. The Fluent Builder Pattern can be adapted to support immutable objects by ensuring that all properties are set at object creation and cannot be changed afterward.

public class Car
{
    public string Make { get; }
    public string Model { get; }
    public int Year { get; }
    public string Color { get; }

    private Car(string make, string model, int year, string color)
    {
        Make = make;
        Model = model;
        Year = year;
        Color = color;
    }

    public class Builder
    {
        private string _make;
        private string _model;
        private int _year;
        private string _color;

        public Builder WithMake(string make)
        {
            _make = make;
            return this;
        }

        public Builder WithModel(string model)
        {
            _model = model;
            return this;
        }

        public Builder WithYear(int year)
        {
            _year = year;
            return this;
        }

        public Builder WithColor(string color)
        {
            _color = color;
            return this;
        }

        public Car Build()
        {
            return new Car(_make, _model, _year, _color);
        }
    }
}
This immutable approach ensures that once the Car object is built, its properties cannot be altered.

   

Wrapping up

   

The Fluent Builder Pattern is a versatile and powerful approach to object construction in C#.
It addresses common issues like complex constructors, optional parameters, and ensures better code readability. By extending the pattern with advanced techniques such as enforcing property setting order, supporting immutability, and handling complex object hierarchies, you can create robust and maintainable code.

 

Whether you're building simple objects or intricate hierarchies, the Fluent Builder Pattern can significantly improve the quality of your code by promoting modularity, flexibility, and readability.
Start incorporating it into your C# projects today to streamline your object creation process!

 

That's all from me today.

 

P.S. If you want to see some more examples of this pattern or 9 more patterns I explained in my ebook "Design Patterns Simplified", you can check out it here.

 

1200+ enigneers already read it.

Join 13,250+ subscribers to improve your .NET Knowledge.

There are 3 ways I can help you:

Design Patterns Simplified ebook

Go-to resource for understanding the core concepts of design patterns without the overwhelming complexity. In this concise and affordable ebook, I've distilled the essence of design patterns into an easy-to-digest format. It is a Beginner level. Check out it here.


Sponsorship

Promote yourself to 13,250+ subscribers by sponsoring this newsletter.


Join TheCodeMan.net Newsletter

Every Monday morning, I share 1 actionable tip on C#, .NET & Arcitecture topic, that you can use right away.


Subscribe to
TheCodeMan.net

Subscribe to the TheCodeMan.net and be among the 13,250+ subscribers gaining practical tips and resources to enhance your .NET expertise.