As we concluded in our previous blog, there are some crucial issues that we still need to consider, so let's talk about them in this one. In our previous blog, we learned about the fundamentals of the builder pattern and why it is necessary to refactor our code to expose a better interface.
Fluency in Builder Inheritance:
You may be wondering what fluency is. And why does it matter? Please allow me to clarify that: fluent interface is not a term specifically associated with the Builder Pattern; in this, we attempt to return the same class object in every method so that the user may chain the various methods on the same class. Let's understand this with the aid of an example.
var sb = new StringBuilder();
sb.AppendLine("This is an example of fluent interface ")
.Append("where we can chain multiple methods on same object ")
.Append("cause all methods from StringBuilder class return same class object");
You can see that the code above is the ideal illustration of fluency because almost all methods return “this”, which refers to the current instance, so the user doesn't need to worry about the object and can chain methods without even thinking about it.
As we are all aware, the Builder pattern is a creational pattern that typically focuses on object creation. It shapes classes in such a way that users can easily create complex objects, but as we create classes, we also need to be mindful of the class properties like Inheritance, etc.
It is very common to inherit from pre-existing builders, but if we are using a fluent interface, we must be mindful of one particular aspect that we will comprehend through the use of a problem.
public class Employee
{
public Guid EmployeeId { get; set; }
public decimal Salary { get; set; }
}
public class EmployeeIdBuilder
{
protected Employee Employee = new Employee();
public EmployeeIdBuilder GenerateNewId()
{
Employee.EmployeeId = Guid.NewGuid();
return this;
}
}
As you can see, we have a builder called "EmployeeIdBuilder" that is responsible for creating a unique identifier for each employee. Now, let's say we need to create another builder for setting Employee Salary because we chose to use the Open-Closed principle and we decided not to touch the existing builder instead of extending it. Let's create that and try to build a new employee object.
public class EmployeeSalaryBuilder : EmployeeIdBuilder
{
public EmployeeSalaryBuilder AddEmployeeSalary(decimal Salary)
{
Employee.Salary = Salary;
return this;
}
}
Let's use this class to create a new employee object since it was created from an existing Builder.
var employeeBuilder = new EmployeeIdBuilder()
.GenerateNewId()
.AddEmployeeSalary(2000);
The issue arises because we are using a fluent interface, which should allow the "AddEmployeeSalary()" method to chain on the already-existing method "GenerateNewId()" but does not. You might be wondering why this is the case. Because "EmployeeSalaryBuilder" is an inheriting class of "EmployeeIdBuilder," if you look closely, the "GenerateNewId()" method returns "EmployeeIdBuilder" while the "AddEmployeeSalary()" method returns "EmployeeSalaryBuilder," and once we call the first method, it prevents us from calling methods from “EmployeeSalaryBuilder.”.
You may now be wondering how we can resolve this, and the straightforward solution is generics. We can use recursive generics to inform base class about the existence of child class, which will resolve our problem because base class will learn about the existence of child classes and that can handle fluency.
public abstract class EmployeeBuider
{
protected Employee Employee = new Employee();
public Employee Create()
{
return Employee;
}
}
public class EmployeeIdBuilder<CURRENT>
: EmployeeBuider
where CURRENT : EmployeeIdBuilder<CURRENT>
{
public CURRENT GenerateNewId()
{
Employee.EmployeeId = Guid.NewGuid();
return (CURRENT)this;
}
}
public class EmployeeSalaryBuilderCURRENT>
: EmployeeIdBuilder<EmployeeSalaryBuilder<CURRENT>>
where CURRENT : EmployeeSalaryBuilder<CURRENT>
{
public CURRENT AddEmployeeSalary(decimal Salary)
{
Employee.Salary = Salary;
return (CURRENT)this;
}
}
As you can see, the "EmployeeSalaryBuilder" class inherits from the "EmployeeIdBuilder" class while also passing its own class instance. As a result, the generic argument "CURRENT" from the "EmployeeIdBuilder" class will point to the "EmployeeSalaryBuilder" class, enabling us to call methods from extended interfaces, as well as all further extensions.
We now face the same issue as before: because we are using generics, we are unable to pass any class types to the EmployeeIdBuilder generic type, making it impossible to create an object of that builder or any other builder. Fortunately, we can easily fix this by introducing our own Builder from the Employee class, and the final code will look something like this:
namespace BuilderPattern.BuilderInheritanceWithFluentSupport
{
public class Employee
{
public Guid EmployeeId { get; set; }
public decimal Salary { get; set; }
public class Builder : EmployeeSalaryBuilder<Builder> { }
public static Builder CreateBuilder() { return new Builder(); }
}
public abstract class EmployeeBuider
{
protected Employee Employee = new Employee();
public Employee Create()
{
return Employee;
}
}
public class EmployeeIdBuilder<CURRENT>
: EmployeeBuider
where CURRENT : EmployeeIdBuilder<CURRENT>
{
public CURRENT GenerateNewId()
{
Employee.EmployeeId = Guid.NewGuid();
return (CURRENT)this;
}
}
public class EmployeeSalaryBuilderCURRENT>
: EmployeeIdBuilder<EmployeeSalaryBuilder<CURRENT>>
where CURRENT : EmployeeSalaryBuilder<CURRENT>
{
public CURRENT AddEmployeeSalary(decimal Salary)
{
Employee.Salary = Salary;
return (CURRENT)this;
}
}
public class Fluency
{
public static void Main(string[] args)
{
var employee = Employee. CreateBuilder()
.GenerateNewId()
.AddEmployeeSalary(2000)
.Create();
Console.WriteLine($"Id: {employee.EmployeeId}, Salary: {employee.Salary}");
}
}
}
Now, this will successfully create an employee object using the Builder, which supports recursive generics, but one thing to note here is the most important part, let's take a close look at the newly created Builder of Employee class.
public class Employee
{
public Guid EmployeeId { get; set; }
public decimal Salary { get; set; }
public class Builder : EmployeeSalaryBuilder<Builder> { }
public static Builder CreateBuilder() { return new Builder(); }
}
The Builder class in the above code snippet is inheriting from the newly created builder rather than the one we already had, because the newly created builder will automatically notify the parent with a Builder class instance, and the parent will notify it's parent with its own type holding a Builder object.
The generic chain will look something like this
Builder
will notify to
EmployeeSalaryBuilder<Builder>
will notify to
EmployeeIdBuilder<EmployeeSalaryBuilder<Builder>>
And that’s what make chaining of methods possible.
So this was the brief about the fluency in Builder Inheritance and the problems we may face while inheriting from existing Builders, I would highly suggest looking into the recursive inheritance, if you are already using inheritance with Builders, cause at some point in time you will face the same issue and you should be prepared to solve that.
Also in our next blog we will see different types of Builders with respective examples, till then.
Happy Coding…!!!
Top comments (0)