Builder Pattern

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.