The Builder design pattern is a creational design pattern that lets us create an object one step at a time. It is quite common to use this pattern when creating a complex object. By using this pattern, we can create different parts of an object, step by step, and then connect all the parts together.
Without this pattern, we can end up with a large constructor to provide all the required parameters for constructing our object. That could lead to quite unreadable and hardly maintainable code. Furthermore, a constructor with lots of parameters has a downside to it. We won’t need to use all the parameters, all the time.
In this article, we are going to show you how to implement the Builder pattern to avoid such complex constructors. We will go even further and explain the Builder Recursive Generics design pattern and Faceted Builder design pattern as well in our next articles.
For the main page of this series check out C# Design Patterns.
VIDEO: Builder and FluentBuilder Design Patterns in C#.
Implementing the Builder Design Pattern
We are going to write a simple example of creating a stock report for all the products in our store.
So, let us start with a simplified Product
class:
public class Product { public string Name { get; set; } public double Price { get; set; } }
We are going to use this class just for storing some basic data about a single product.
Our stock report object is going to consist of the header, body and footer parts. So, it is quite logical to divide the object building process into those three actions. First, let’s start with the ProductStockReport
class:
public class ProductStockReport { public string HeaderPart { get; set; } public string BodyPart { get; set; } public string FooterPart { get; set; } public override string ToString() => new StringBuilder() .AppendLine(HeaderPart) .AppendLine(BodyPart) .AppendLine(FooterPart) .ToString(); }
This is the object, we are going to build with the Builder design pattern.
To continue on, we need a builder interface to organize the building process:
public interface IProductStockReportBuilder { void BuildHeader(); void BuildBody(); void BuildFooter(); ProductStockReport GetReport(); }
We can see that the concrete builder class which is going to implement this interface, needs to create all the parts for our stock report object and return that object as well. So, let’s implement our concrete builder class:
public class ProductStockReportBuilder : IProductStockReportBuilder { private ProductStockReport _productStockReport; private IEnumerable<Product> _products; public ProductStockReportBuilder(IEnumerable<Product> products) { _products = products; _productStockReport = new ProductStockReport(); } public void BuildHeader() { _productStockReport.HeaderPart = $"STOCK REPORT FOR ALL THE PRODUCTS ON DATE: {DateTime.Now}\n"; } public void BuildBody() { _productStockReport.BodyPart = string.Join(Environment.NewLine, _products.Select(p => $"Product name: {p.Name}, product price: {p.Price}")); } public void BuildFooter() { _productStockReport.FooterPart = "\nReport provided by the IT_PRODUCTS company."; } public ProductStockReport GetReport() { var productStockReport = _productStockReport; Clear(); return productStockReport; } private void Clear() => _productStockReport = new ProductStockReport(); }
This logic is quite straight forward. We receive all the products required for our report and instantiate the _productStockReport
object. Then, we create all the parts of our object and finally return it. In the GetReport
method, we reset our object and prepare a new instance to be ready to create another report. This is usual behavior but it is not mandatory.
Once our building logic is over, we can start building our object in a client class or even encapsulate the building process from the client class inside a Director class. Well, this is exactly what we are going to do:
public class ProductStockReportDirector { private readonly IProductStockReportBuilder _productStockReportBuilder; public ProductStockReportDirector(IProductStockReportBuilder productStockReportBuilder) { _productStockReportBuilder = productStockReportBuilder; } public void BuildStockReport() { _productStockReportBuilder.BuildHeader(); _productStockReportBuilder.BuildBody(); _productStockReportBuilder.BuildFooter(); } }
Creating the StockReport Object
After we have finished all this work, we can start building our object:
class Program { static void Main(string[] args) { var products = new List<Product> { new Product { Name = "Monitor", Price = 200.50 }, new Product { Name = "Mouse", Price = 20.41 }, new Product { Name = "Keyboard", Price = 30.15} }; var builder = new ProductStockReportBuilder(products); var director = new ProductStockReportDirector(builder); director.BuildStockReport(); var report = builder.GetReport(); Console.WriteLine(report); } }
The result :
Excellent. We have created our object with the Builder design pattern.
Fluent Builder
The Fluent builder is a small variation of the Builder design pattern, which allows us to chain our builder calls towards different actions. To implement the Fluent builder, we are going to change the builder interface first:
public interface IProductStockReportBuilder { IProductStockReportBuilder BuildHeader(); IProductStockReportBuilder BuildBody(); IProductStockReportBuilder BuildFooter(); ProductStockReport GetReport(); }
We have to modify the implementation of the ProductStockReportBuilder
class as well:
public IProductStockReportBuilder BuildHeader() { _productStockReport.HeaderPart = $"STOCK REPORT FOR ALL THE PRODUCTS ON DATE: {DateTime.Now}\n"; return this; } public IProductStockReportBuilder BuildBody() { _productStockReport.BodyPart = string.Join(Environment.NewLine, _products.Select(p => $"Product name: {p.Name}, product price: {p.Price}")); return this; } public IProductStockReportBuilder BuildFooter() { _productStockReport.FooterPart = "\nReport provided by the IT_PRODUCTS company."; return this; }
As a result of these modifications, we can chain the calls in the Director
class:
public void BuildStockReport() { _productStockReportBuilder .BuildHeader() .BuildBody() .BuildFooter(); }
If we start our application now, the result is going to be the same, but this time we use the fluent interface.
Conclusion
In this article, we have learned about how to create Builder Design Pattern and how to implement it into our project to create complex objects. Furthermore, we have expanded our example to use the Fluent interface, which allows us to chain our Builder calls together.
In the next article, we are going to learn how to implement Fluent Builder Interface With Recursive Generics.