Hello everyone, and welcome to .NET Core Central. In this blog post, I am going to walk through the Builder design pattern. The Builder design pattern is also a creational design pattern just like the Factory Method design pattern and the Abstract Factory design pattern.
The main intent of the Builder Pattern is to simplify the construction of a complex object. And the way we achieve this is by separating the construction of the object from the type of the object itself. This means we don’t use the new keyword to create the object, instead, we use a separate builder to build the object. And that is the main idea of the builder pattern.
Implementation of Builder Pattern
For the implementation of the builder pattern, we will consider a scenario of creating a user object. For the purpose of this application, I will use Visual Studio 2022 with .NET 6 and C# 10.
namespace BuilderPattern.Demo;
public class User
{
private string name;
private int age;
private string address;
}
Options of creating objects
If we need to create an instance of the type User
traditionally, we will have a constructor. And in the constructor, we are going to take the name, age, and address and set those to the instance members.
But what if you want to create the object with only the name in one situation and in another situation with only the age or for another situation with the address. In that case, we can do it by creating three different constructors. If we do that the code will look a little bit ugly.
The other way to implement this is by using some default constructor parameters. In that case, we don’t have to pass everything every time. This implementation is also a little bit ugly and unintuitive.
Finally, we can allow getter/setter properties. But if we allow the set and get method on the property, essentially we lose the immutability of the object. Meaning the object is not immutable.
The solution with Builder pattern
So how do you do it? This is where we will use the Builder pattern.
In the case of the Builder pattern, what we will do is create a builder class. And the builder class will be an inner class inside of the User
class itself.
Because the builder class will be responsible for building the User
class, we to will have a private constructor for the User
class.
Also, we will create an interface IUser
, which will have three properties, Name
, Age
, and Address
. The User
class will implement the IUser
interface.
namespace BuilderPattern.Demo;
public interface IUser
{
string Name { get; }
string Address { get; }
int Age { get; }
}
public class User : IUser
{
private string name;
private int age;
private string address;
private User() { }
public string Name => name;
public string Address => address;
public int Age => age;
}
Builder class
Now it is time to create a builder class. And this class will be an inner class of the User
class.
namespace BuilderPattern.Demo;
public interface IUser
{
string Name { get; }
string Address { get; }
int Age { get; }
}
public class User : IUser
{
private string name;
private int age;
private string address;
private User() { }
public string Name => name;
public string Address => address;
public int Age => age;
public class UserBuilder
{
private readonly User user;
public UserBuilder()
{
user = new User();
}
}
}
In UserBuilder
class, what we are going to do first is, we are going to declare a private User
object. And then we are going to have a public default constructor. And inside the constructor, we are going to create a new instance of the User
.
The main role of the UserBuilder
class is to simplify the creation of the User
instance. Hence instead of the constructor of the User
class taking multiple parameters, the UserBuilder
will provide methods to set the state of the User
class.
Firstly we are going to create a method for setting the name property. And this method is going to return the instance of the UserBuilder
instance itself so that the caller can use fluent API to create the object.
namespace BuilderPattern.Demo;
public interface IUser
{
string Name { get; }
string Address { get; }
int Age { get; }
}
public class User : IUser
{
private string name;
private int age;
private string address;
private User() { }
public string Name => name;
public string Address => address;
public int Age => age;
public class UserBuilder
{
private readonly User user;
public UserBuilder()
{
user = new User();
}
public UserBuilder WithName(string name)
{
user.name = name;
return this;
}
}
}
Similarly, I will create two new methods, WithAge
and WithAddress
to allow to set the other two properties of the User
class. And finally, I will create a method called Build
, which will return the User
object.
namespace BuilderPattern.Demo;
public interface IUser
{
string Name { get; }
string Address { get; }
int Age { get; }
}
public class User : IUser
{
private string name;
private int age;
private string address;
private User() { }
public string Name => name;
public string Address => address;
public int Age => age;
public class UserBuilder
{
private readonly User user;
public UserBuilder()
{
user = new User();
}
public UserBuilder WithName(string name)
{
user.name = name;
return this;
}
public UserBuilder WithAge(int age)
{
user.age = age;
return this;
}
public UserBuilder WithAddress(string address)
{
user.address = address;
return this;
}
public User Build() => user;
}
}
So now as you can see the User
object cannot be created outside without the UserBuilder
.
One of the most important features of the builder pattern is that it retains the immutability of the class if the instance is created as a part of the dependency injection. As you can see we are not losing immutability because the state of the User
class can only be set through the UserBuilder
and this class will be created during the dependency injection.
How to call Builder class
Now let us look into how we are going to use the builder class. So let’s say we want to create the User
object and let us say we want to do it during dependency injection.
I am going to do that in the Program
class.
using BuilderPattern.Demo;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSingleton<IUser>(new User.UserBuilder()
.WithName("John")
.WithAddress("123 Street")
.Build());
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
As you can see we create a new instance of UserBuilder
and then use the methods of the UserBuilder
class to create the instance of the User class.
As you can see this is how we can build the User
using the UserBuilder
considerably easily. And you might have seen a similar pattern, for example, if you see in the Program class, you see the WebApplicationBuilder
class uses Builder pattern to create the WebApplication
instance.
Conclusion
In my experience, this is a pattern that you will not use on day to day basis. There are only a few corner cases where this will be useful. But this definitely very useful in this kind of situation where you want to reduce the complexity of an object creation that needs different sets of attributes in different scenarios.
So this covers how you can use the Builder pattern to build a complex object.
A Youtube video for this implementation is here.