Design patterns in C# - The Builder Pattern
So, the other week I took a look at the Factory Method and Abstract Factory design patterns. This week I'm continuing with the creational design patterns, this time focusing on the builder pattern!
The builder pattern is used when there is complex set up involved in creating an object. Like the other creational patterns, it also separates out the construction of an object from the object's use. The idea is that the builder allows you to do more complex set up for which a constructor would not be adequate. One example of this is if there is a set order in which set up steps need to happen. This can be achieved via an abstract base class which defines the order in terms of a set of steps. The individual steps are then overwritten for each type of the object being built.
In-keeping with the dinosaur theme, I thought we'd build some nice meals for our dinosaurs.
Here is our meal class:
public class Meal
{
public string Starter { get; set; }
public string MainCourse { get; set; }
private Dessert Dessert;
public void SetDessert(string dessertName)
{
Dessert = new Dessert(dessertName);
}
public void AddTopping(string topping)
{
Dessert.AddTopping(topping);
}
public void ServeMeal()
{
Console.WriteLine($"Bringing you {Starter}, " +
$"then {MainCourse}, " +
$"then {Dessert.Name} with {Dessert.ListToppings()} on top");
}
}
It defines properties for the starter and main, and a field to hold the dessert. The Dessert
class looks like this:
public class Dessert
{
public string Name { get; set; }
private readonly IList<string> toppings = new List<string>();
public Dessert(string dessertName)
{
Name = dessertName;
}
public void AddTopping(string topping)
{
toppings.Add(topping);
}
public string ListToppings()
{
return ProduceToppingString(toppings);
}
}
Here the ProduceToppingString
method just formats the list of toppings as a string, and is used here to simplify the example code.
The Meal
class also provides a ServeMeal
method used to print out the meal to the console.
Now, we could just initialise a Meal
as follows:
static void CreateAMealExample()
{
var meal = new Meal();
meal.Starter = "a few green leaves";
meal.MainCourse = "a huge plate of ferns";
meal.SetDessert("fruit and berries");
meal.ServeMeal();
}
However, as we can see here, our herbivore doesn't have any toppings on her meal! This is a pretty simple example, and there are only 4 things to remember to do, but you can see how it would be easy to forget steps in the process. In order to produce a meal in a consistent state, all of these properties need to be initialised. And this must also be done in the correct order, can't have the dessert being ready before the starter! (And crucially, the Dessert
needs to be initialised before any toppings can be added!) The final issue with this, is that there is no way to reproduce a specific meal for say, a stegosaurus...
If we start with a meal builder:
public abstract class MealBuilder
{
protected Meal meal = new Meal();
public Meal BuildMeal()
{
AddStarter();
AddMain();
AddDessert();
AddToppings();
return meal;
}
protected abstract void AddStarter();
protected abstract void AddMain();
protected abstract void AddDessert();
protected abstract void AddToppings();
}
This defined the order in which the steps will be taken, and abstract methods for each step in the process.
We then override the steps in a concrete implementation:
public class StegosaurusMealBuilder : MealBuilder
{
protected override void AddStarter()
{
this.meal.Starter = "a few green leaves";
}
protected override void AddMain()
{
this.meal.MainCourse = "a huge plate of ferns";
}
protected override void AddDessert()
{
this.meal.SetDessert("fruit and berries");
}
protected override void AddToppings()
{
this.meal.AddTopping("crunchy flowers");
}
}
This sets the various properties on the meal field in the builder base class. When BuildMeal
is called on a builder, these steps will be run in order and then the Meal will be returned.
So, when we call the following code:
public static void BuilderExample()
{
MealBuilder builder = new StegosaurusMealBuilder();
Meal meal = builder.BuildMeal();
meal.ServeMeal();
}
The following will be written out to the console:
If we then want to build a different kind of meal, e.g. one for our TRex, we would write another implementation of the MealBuilder
base class:
And if we instead use a TRexMealBuilder
in our example we would get the following output:
In this way, we have defined the order in which a Meal
object must be created. We have created a set of steps which can be overwritten for different sorts of Meal
. And finally, we have made sure that Meal
objects are always in a consistent state!
Now, this is pretty good, but it does mean that we end up with a complex class hierarchy in which we need one builder for each type of meal. In a world of dependency injection, and maximising extensibility, is there a better way?
When using dependency injection in ASP.NET Core you can pass your own delegates into the builder in order to build the types you are registering, exchanging inheritance for composition. What would this look like for our dinosaur meal? This allows you to compose the builder yourself, as part of the set up for the application, instead of forcing you to derive from a base class for each new scenario. This is extremely extensible and powerful, and reduces the need for large class hierarchies within your application.
Here's a link to the GitHub repository if you want to have a look round the solution, otherwise look out for my next blog on design patterns in C#!