In software development, sometimes we need to create complex objects that contain multiple fields and configuration options. Even if we try to build such objects using a constructor, we may end up having too many parameters, which obviously makes the code hard to read and difficult to maintain.
This is where Builder Design Pattern becomes useful.
Builder Pattern is a creational design pattern that helps to construct the complex objects step by step. It separates the object construction process from its representation and allows the same construction process to create different representations.
The Problem: Creating Complex Objects
Imagine we are building an HR management system. While working on the Add Employee feature we need to create an employee object. An employee might need the following information: FirstName, LastName, Email, Department, Position, Salary, Health Insurance, Pension Plan etc. If we use a constructor, the code might become like this:
public Employee(string firstName,
string lastName,
string email,
string department,
string position,
decimal salary,
bool hasHealthInsurance,
bool hasPensionPlan)
{
this.FirstName = firstName;
// Other initializations
}
Now, creating an employee object look like this:
var employee = new Employee(
"Nabaraj",
"Ghimire",
"[email protected]",
"IT",
"Senior Developer",
100000,
true,
true
);
Problems with this approach
Components and Step by Step implementation
Product: The object being created
Builder: Defines steps to build the object
Concrete Builder: Implements the building steps
Director (optional): Controls the building process
Client: Uses the builder
Step 1: Product Class
Let’s define an Employee class.
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public string Position { get; set; }
public decimal Salary { get; set; }
public bool HasHealthInsurance { get; set; }
public bool HasPensionPlan { get; set; }
}
Step 2: Builder Interface
Let’s define the steps required to build an Employee.
public interface IEmployeeBuilder
{
IEmployeeBuilder SetFirstName(string firstName);
IEmployeeBuilder SetLastName(string lastName);
IEmployeeBuilder SetEmail(string email);
IEmployeeBuilder SetDepartment(string department);
IEmployeeBuilder SetPosition(string position);
IEmployeeBuilder SetSalary(decimal salary);
IEmployeeBuilder AddHealthInsurance();
IEmployeeBuilder AddPensionPlan();
Employee Build();
}
Step 3: Concrete Builder
Let’s implement the Builder.
public class EmployeeBuilder : IEmployeeBuilder
{
private Employee _employee = new Employee();
public IEmployeeBuilder SetFirstName(string firstName)
{
_employee.FirstName = firstName;
return this;
}
public IEmployeeBuilder SetLastName(string lastName)
{
_employee.LastName = lastName;
return this;
}
public IEmployeeBuilder SetEmail(string email)
{
_employee.Email = email;
return this;
}
public IEmployeeBuilder SetDepartment(string department)
{
_employee.Department = department;
return this;
}
public IEmployeeBuilder SetPosition(string position)
{
_employee.Position = position;
return this;
}
public IEmployeeBuilder SetSalary(decimal salary)
{
_employee.Salary = salary;
return this;
}
public IEmployeeBuilder AddHealthInsurance()
{
_employee.HasHealthInsurance = true;
return this;
}
public IEmployeeBuilder AddPensionPlan()
{
_employee.HasPensionPlan = true;
return this;
}
public Employee Build()
{
return _employee;
}
}
Step 4: Client Code
Now we can create Employee like this:
var employee = new EmployeeBuilder()
.SetFirstName("Nabaraj")
.SetLastName("Ghimire")
.SetEmail("[email protected]")
.SetDepartment("IT")
.SetPosition("Senior Software Engineer")
.SetSalary(100000)
.AddHealthInsurance()
.AddPensionPlan()
.Build();
Director in Builder Pattern
In the Builder Pattern, a Director is responsible for controlling the construction process. Instead of the client calling all builder methods manually, the Director defines predefined ways to build objects.
The Director works with the builder interface, which allows it to use different builders without knowing their concrete implementations.
In an HR system, a Director can be used to create common employee types such as Software Developer, HR Manager, and Intern
Example of EmployeeDirector:
public class EmployeeDirector
{
public Employee CreateDeveloper(IEmployeeBuilder builder)
{
return builder
.SetDepartment("Engineering")
.SetPosition("Software Developer")
.SetSalary(85000)
.AddHealthInsurance()
.Build();
}
public Employee CreateManager(IEmployeeBuilder builder)
{
return builder
.SetDepartment("Management")
.SetPosition("Manager")
.SetSalary(100000)
.AddHealthInsurance()
.AddPensionPlan()
.Build();
}
}
Now we can use Director as:
var builder = new EmployeeBuilder();
var director = new EmployeeDirector();
var developer = director.CreateDeveloper(builder);
Console.WriteLine(developer.Position);
Advantages of Director
Standardizes object creation
Reuses common configurations
Keeps client code cleaner
Allows different builders to be used
Immutable Builder
Sometimes, especially in enterprise and multi-threaded systems, objects are often designed to be immutable. An immutable object means its state cannot be changed after it is created. This improves thread safety and Code reliability.
For Immutable Builder, instead of public setters, we should use the read only properties which can’t be modified after creation.
public class Employee
{
public string FirstName { get; }
public string LastName { get; }
public string Department { get; }
public string Position { get; }
public decimal Salary { get; }
public bool HasHealthInsurance { get; }
public bool HasPensionPlan { get; }
public Employee(
string firstName,
string lastName,
string department,
string position,
decimal salary,
bool hasHealthInsurance,
bool hasPensionPlan)
{
FirstName = firstName;
LastName = lastName;
Department = department;
Position = position;
Salary = salary;
HasHealthInsurance = hasHealthInsurance;
HasPensionPlan = hasPensionPlan;
}
}
Now we create a builder class that collects value then creates an immutable object.
public class EmployeeBuilder
{
private string _firstName;
private string _lastName;
private string _department;
private string _position;
private decimal _salary;
private bool _hasHealthInsurance;
private bool _hasPensionPlan;
public EmployeeBuilder SetFirstName(string firstName)
{
_firstName = firstName;
return this;
}
public EmployeeBuilder SetLastName(string lastName)
{
_lastName = lastName;
return this;
}
public EmployeeBuilder SetDepartment(string department)
{
_department = department;
return this;
}
public EmployeeBuilder SetPosition(string position)
{
_position = position;
return this;
}
public EmployeeBuilder SetSalary(decimal salary)
{
_salary = salary;
return this;
}
public EmployeeBuilder AddHealthInsurance()
{
_hasHealthInsurance = true;
return this;
}
public EmployeeBuilder AddPensionPlan()
{
_hasPensionPlan = true;
return this;
}
public Employee Build()
{
return new Employee(
_firstName,
_lastName,
_department,
_position,
_salary,
_hasHealthInsurance,
_hasPensionPlan
);
}
}
Now from client code we can create an immutable object. Once this object is created, we can’t modify it.
var employee = new EmployeeBuilder()
.SetFirstName("Nabaraj")
.SetLastName("Ghimire")
.SetDepartment("Engineering")
.SetPosition("Senior Developer")
.SetSalary(100000)
.AddHealthInsurance()
.Build();
Advantages of Immutable builder
Conclusion
The Builder Pattern is a powerful creational design pattern that helps construct complex objects step by step. Without using large constructors with many parameters, the Builder Pattern allows us to create objects in a clear, flexible, and maintainable way.
In enterprise systems such as HR, ERP, and Payroll applications, where objects often contain many optional properties, the Builder Pattern can significantly improve code readability and maintainability.