Now let's talk about the builder pattern. What problem does it solve?
In C#, let's say we want to construct a user object which requires 3 pieces of information, but the process is split into 3 methods. Naively we might write some code like:
User CreateUser()
{
var id = "new-user-id";
return CreateUserStep2(id);
}
User CreateUserStep2(string id)
{
var name = "Foobar";
return CreateUserStep3(id, name);
}
User CreateUserStep3(string id, string name)
{
var isAlive = true;
return new User(id, name, isAlive);
}
This works, but this code is extremely fragile. Imagine if later we changed id to a GUID instead of a string, now you need to update all of the CreateUserStep* methods.
Instead, let's use a builder:
User CreateUser()
{
var builder = new UserBuilder();
builder.SetId("new-user-id");
return CreateUserStep2(builder);
}
User CreateUserStep2(UserBuilder builder)
{
builder.SetName("Foobar");
return CreateUserStep3(builder);
}
User CreateUserStep3(UserBuilder builder)
{
builder.SetIsAlive(true);
return builder.Build();
}
Perfect, now if id changes type, we only need to change the builder itself and the specific step that creates the id, while other steps are completely unaffected. Our code is now much more refactor friendly.
However that's not the end yet. When CreateUserStep3 receives a builder, it has no way to know whether the other properties have been set or not, and thus calling .Build() might not be safe. Let's further refactor it:
interface IUserBuilderStep1
{
IUserBuilderStep2 SetId(string id);
}
interface IUserBuilderStep2
{
IUserBuilderStep3 SetName(string name);
}
interface IUserBuilderStep3
{
IUserBuilderCompleted SetIsAlive(bool isAlive);
}
interface IUserBuilderCompleted
{
User Build();
}
Now we can guarantee the builders are called in order, and nothing is missed when building.