In modern software development, creating complex objects in a simple and maintainable way is crucial for readability and efficiency. This post delves into the Fluent Builder Pattern, a creational pattern designed to simplify the creation of complex objects. This approach is particularly useful in scenarios where multiple constructors or configurations are needed, such as building reports, CSV exports, or test objects.
The Fluent Builder Pattern streamlines the process of creating complex objects. This pattern uses method chaining to set properties, enhancing code readability and reducing mental overload. By using a Fluent API, developers can achieve expressive and concise syntax, making the code more readable and maintainable.
Here's a simple example creating a order builder that you would find in a e-commerce application.
public class Order
{
public int OrderNumber { get; init; }
public DateTime CreatedOn { get; init; }
public Address ShippingAddress { get; init; }
}
public class OrderBuilder
{
private int _orderNumber;
private DateTime _createdOn;
private Address _shippingAddress;
private OrderBuilder() { }
public static OrderBuilder Create() => new OrderBuilder();
public OrderBuilder OrderNumber(int number)
{
_orderNumber = number;
return this;
}
public OrderBuilder OrderedOn(DateTime createdOn)
{
_createdOn = createdOn;
return this;
}
public Order Build() => new Order
{
OrderNumber = _orderNumber,
CreatedOn = _createdOn,
};
}
Naming conventions are essential for the readability of the Fluent Builder Pattern. Common practices include using prefixes like With
, but a more expressive approach can be used. For instance, instead of WithReleaseDate
, simply use ReleaseDate
.
Using the builder pattern.
var order = Order.OrderBuilder.Create()
.OrderNumber(1001)
.OrderedOn(DateTime.UtcNow)
.Build();
Builder Inside a Builder
In scenarios where nested objects are involved, such as a Shipping Address, a builder can be nested inside another builder. This approach simplifies the creation of nested objects
public class Address
{
public string Street { get; init; }
public string City { get; init; }
public string State { get; init; }
public string ZipCode { get; init; }
public string Country { get; init; }
}
public class AddressBuilder
{
private string _street;
private string _city;
private string _state;
private string _zipCode;
private string _country;
private AddressBuilder() { }
public static AddressBuilder Create() => new AddressBuilder();
public AddressBuilder Street(string street)
{
_street = street;
return this;
}
public AddressBuilder City(string city)
{
_city = city;
return this;
}
public AddressBuilder State(string state)
{
_state = state;
return this;
}
public AddressBuilder ZipCode(string zipCode)
{
_zipCode = zipCode;
return this;
}
public AddressBuilder Country(string country)
{
_country = country;
return this;
}
public Address Build() => new Address
{
Street = _street,
City = _city,
State = _state,
ZipCode = _zipCode,
Country = _country
};
}
Such chaining is helpful to create complex object structure.
Ensuring Required Properties
Sometimes it's necessary to ensure that certain required properties are set before building an object. These types of builders are generally known as Guided Builders. In the example below, I have created three separate classes for each of the required properties or fields of the customer inside the customer builder. Please note that the subclasses shown here can be in separate files by themselves. Such guided builders ensure that the properties have to be set in a particular order before creating the object. Though this might be a bit verbose, it is very helpful in certain cases where we have to build SDKs and class libraries to be used by other developers.
public class GuidedCustomerBuilder
{
public static RequiredFirstName Create()
{
}
public class RequiredFirstName
{
}
public class RequiredLastName
{
public RequiredBirthYear WithLastName(string lastName)
{
}
}
public class RequiredBirthYear
{
public RequiredBirthYear BornIn(int birthYear)
{
return this;
}
public Customer Build()
{
return new Customer();
}
}
}
Take a look at my GitHub repo on Fluent Design Pattern for a more detailed comprehensive code sample.