DEV Community

Cover image for Stepwise “Builder”
Pranil Tunga
Pranil Tunga

Posted on

Stepwise “Builder”

In our previous blog, we discussed the necessity of builder inheritance fluency as well as the benefits of occasionally extending an existing builder while still adhering to the SOLID principles (i.e., by using the Open-Closed principle). Here, we'll talk about a very unique situation involving builders that may come in handy when stepwise object creation is required.

Let's look at an example: in our previous blog, we created the Employee class, but now the requirements have changed and we must set salary based on the designation, so take a look at our new "Employee" class.

public enum Designations
{
    Employee,
    Manager,
    TechnicalLead
}

public class Employee
{
    public Guid Id { get; set; }
    public Designations Designation { get; set; }
    public decimal Salary { get; set; }
}

public class EmployeeBuilder
{
    private Employee _employee = new Employee();

    public EmployeeBuilder Create(Designations desg, decimal salary)
    {
        _employee = new Employee
        {
            Id = Guid.NewGuid(),
            Designation = desg,
            Salary = salary
        };

        return this;
    }

    public Employee Build()
    {
        return _employee;
    }
}
Enter fullscreen mode Exit fullscreen mode

The example above will satisfy our basic needs for object creation, but there is a very significant flaw in the code. Currently, we are exposing the "Create()" method to the user, and while we accept both the employee's designation and salary, what happens if the user enters the incorrect designation and salary? Since the user must worry about the business logic, this solution does not appear to be ideal. Another thing is that we shouldn't let users set salaries; instead, it should be set to a property according to our business logic. You might wonder what should be done to fix this, and the answer is very straightforward: we can use "Stepwise Builder" to close any loophole that is currently present.

namespace Builder.StepwiseBuilder
{
    public enum Designations
    {
        Employee,
        Manager,
        TechnicalLead
    }

    public interface IDesignationBuilder
    {
        ISalaryBuilder WithDesignation(Designations designation);
    }

    public interface ISalaryBuilder
    {
        IEmployeeBuilder AssignSalary();
    }

    public interface IEmployeeBuilder
    {
        Employee Build();
    }

    public class Employee
    {
        public Guid Id { get; set; }
        public Designations Designation { get; set; }
        public decimal Salary { get; set; }

        public static Builder CreateBuilder()
        {
            return new Builder();
        }

        public class Builder : IDesignationBuilder, ISalaryBuilder, IEmployeeBuilder
        {
            private Employee _employee = new Employee();

            public ISalaryBuilder WithDesignation(Designations designation)
            {
                _employee.Designation = designation;
                return this;
            }

            public IEmployeeBuilder AssignSalary()
            {
                switch (_employee.Designation)
                {
                    case Designations.Employee:
                        _employee.Salary = 1000;
                        break;
                    case Designations.Manager:
                        _employee.Salary = 5000;
                        break;
                    case Designations.TechnicalLead:
                        _employee.Salary = 10000;
                        break;
                    default:
                        throw new InvalidOperationException("Designation not supported, please pass valid designation");
                }

                return this;
            }

            public Employee Build()
            {
                return _employee;
            }
        }
    }

    public class StepWiseBuilderExample
    {
        public static void Main(string[] args)
        {
            var employee = Employee.CreateBuilder()
                                   .WithDesignation(Designations.Employee)
                                   .AssignSalary()
                                   .Build();

            Console.WriteLine($"{employee.Designation} gets paid ${employee.Salary}");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output of an above example

Here, we are using different interfaces for different purposes and later creating a chain so that we can perform operations in specific steps. As you can see, "IDesignationBuilder" is returning "ISalaryBuilder" and so on, so that we can only perform salary-related operations after designation is set. In this way, we are inadvertently forcing users to follow steps and also making sure that Business logic is followed.


Finally, I just wanted to emphasize how important this type of Builder is and how it can be used in many different contexts, especially when performing domain-based programming, where you need to make sure that objects are created with purpose and that users follow the proper procedures to create objects.

So that's it for this blog post. In our following blog, we'll look at more ways to implement builders and talk about some of their applications. Until then.

Happy Coding…!!!

Top comments (0)