Fluent Builder Design Pattern

Fluent Builder is a design pattern that falls under the category of creational design patterns. It is a variation of the Builder Design Pattern and it allows us to chain different builder calls.

Class instantiation

Let's take this class as an example:

public class Company
{
    public Guid Id { get; set; }
    public List<Person> Employees { get; set; }
    public string Title { get; set; }
    public string Address { get; set; }

    public override string ToString() =>
        $"Id: {Id}, Employees: {Employees.Count}, Title: {Title}, Address: {Address}";
}

Initialisation via Constructor

All arguments are passed to the constructor in the correct order. This can be challenging when working with complex classes and is error-prone.

var company = new Company(Guid.NewGuid(), employees, "ACME Corporation", "4113 Little Acres Lane");

Initialisation via Object Initializer

Object initializer lets you assign values to any of the accessible object fields at creation time, without having to invoke a constructor. With object initializer syntax we explicitly specify arguments for a constructor using their name and value. It is also possible to omit arguments. When dealing with immutable objects, that don't have public setters, it is possible to use init keyword, which allows setting the field’s value with the object initializer.

var company = new Company
{
    Id = Guid.NewGuid(),
    Employees = employees, 
    Name = "ACME Corporation",
    Address = "4113 Little Acres Lane"
};

Piecewise object construction

With piecewise object construction you create an empty object first and then set the value of the fields one by one. This requires an empty constructor to exist.

var company = new Company();
company.Id = Guid.NewGuid();
company.Employees = employees;
company.Name= "ACME Corporation";
company.Address= "4113 Little Acres Lane";

Builder

A builder is a component that is responsible for object creation. It lets us create an object step by step. It is useful when dealing with complex objects. Builder provides a separation of creational logic from other parts of the application.

It is possible to overload builder methods to support different use cases of object creation. A builder also supports the creation of immutable objects.

using System;
using System.Collections.Generic;
using BuilderExample.Model;

namespace BuilderExample.Builder;

public class CompanyBuilder
{
    private Company _company = new Company();

    public void AddId(Guid id)
    {
        _company.Id = id;
    }

    public void AddEmployees(List<Employee> employees)
    {
        _company.Employees = employees;
    }

    public void AddName(string name)
    {
        _company.Name = name;
    }

    public void AddAddress(string address)
    {
        _company.Address = address;
    }

    public Company GetCompany()
    {
        return _company;
    }
}

An object could then be created like this:

var companyBuilder = new CompanyBuilder();
companyBuilder.AddId(Guid.NewGuid());
companyBuilder.AddEmployees(employees);
companyBuilder.AddName("ACME Corporation");
companyBuilder.AddUser("4113 Little Acres Lane");
var company = companyBuilder.GetCompany();

Fluent Builder

Fluent Builder uses method chaining to make object creation more readable. There is no need to specify the builder object with every statement anymore. It is necessary to modify the implementation of the builder class:

using System;
using System.Collections.Generic;
using BuilderExample.Model;

namespace BuilderExample.Builder;

public class CompanyFluentBuilder
{
    private Company _company = new Company();

    public CompanyFluentBuilder Id(Guid id)
    {
        _company.Id = id;
        return this;
    }

    public CompanyFluentBuilder Employees(List<Employee> employees)
    {
        _company.Employees = employees;
        return this;
    }

    public CompanyFluentBuilder Name(string name)
    {
        _company.Name = name;
        return this;
    }

    public CompanyFluentBuilder Address(string address)
    {
        _company.Address = address;
        return this;
    }

    public Company Build()
    {
        return _company;
    }
}

Now, we chain the calls when creating the Company object:

var company= new CompanyFluentBuilder()
                            .Id(Guid.NewGuid())
                            .Employees(employees)
                            .Name("ACME Corporation")
                            .Address("4113 Little Acres Lane")
                            .Build();

Takeaways

Fluent Builder can be used in different scenarios. It is useful in testing when creating mock objects. It can also be used when constructing complex queries for searching, pagination etc.

That being said, it is quite difficult to implement inheritance in a Fluent Builder Pattern. One would have to use the Recursive Generics approach, which can make builder logic quite complex.

References: