Introduction
The Builder Pattern is among the Creational Patterns of the Gang Of Four (GOF). To understand this pattern, we first need to understand its intent.
Intent
“Separate the construction of a complex object from its representation so that the same construction process can create different representations.” –GOF
The preceding definition of the Builder Design Pattern is given by the GOF. It might be difficult to understand by reading the definition, so let us understand it by dividing it up.
So the moral of the story is "Separate object construction from its representation".
Now let's understand the implementation of this pattern using a real-world scenario.
Scenario
On one fine day a Pizza vendor has placed an order to a software company. His requirement was to get the list of contents to make a Pizza based on the requested Pizza type. The company has accepted the order and has provided this definition to his Project Manager Mr. X. Then Mr. X has explained all the requirements to his developer Mr. Y and asked him to develop a program for that.
Approach 1
Mr. Y has begun thinking of the definition. He thought that there are many possible contents in a Pizza, like size, dough type, cheese type and so on. So first he created enumerations for all these selections. Then he created one class called "Pizza". In this class he has created a parameterized constructor with all required contents to create the Pizza. This constructor accepts the contents and sets the values in private variables. Now Mr. X has accept the functionality for the contents from the client, so he created a method called "PizzaContent" to print the entire list of contents to generate the Pizza.
- public class Pizza
- {
- private readonly DoughType doughType;
- private readonly bool isRedPepper;
- private readonly Size size;
- private readonly CheeseType cheeseType;
- private readonly List<string> vegetables;
-
- public Pizza(DoughType doughType, bool isRedPepper, Size size,CheeseType cheeseType,List<string> vegetables)
- {
- this.doughType = doughType;
- this.isRedPepper = isRedPepper;
- this.size=size;
- this.cheeseType=cheeseType;
- this.vegetables=vegetables;
- }
-
- public void PizzaContent()
- {
- Console.WriteLine("Pizza with {0}", doughType);
-
- if (isRedPepper)
- Console.WriteLine("Red Pepper");
-
- Console.WriteLine("Size: {0}", size);
- Console.WriteLine("Cheese Type: {0}", cheeseType);
- Console.WriteLine("Vegetables:");
-
- foreach (var item in vegetables)
- {
- Console.WriteLine(" {0}", item);
- }
- }
- }
- public enum DoughType
- {
- Neapolitan_Pizza_Dough = 1,
- NewYorkStyle_Pizza_Dough = 2,
- SquarePan_Pizza_Dough = 3
- }
-
- public enum CheeseType
- {
- American = 1,
- Swiss = 2,
- }
-
- public enum Size
- {
- Small = 1,
- Medium = 2,
- Large = 3
- }
Client-Side Code
Mr. Y has created an object of the Pizza class where he has provided the contents of his choice to create a Pizza and called the method to get the contents.
- static void Main(string[] args)
- {
- new Pizza(DoughType.Neapolitan_Pizza_Dough, true, Size.Medium, CheeseType.Swiss, new List<string> { "Tomato", "Capsicum","Corn" }).PizzaContent();
- Console.ReadKey();
- }
Output
Mr. Y has checked the application and it was built and run successfully.
Mr. Y was very happy that he has completed the given task successfully. He has provided the demo to his Project Manager Mr. X. Mr. X has gone through the application and has discovered a problem. The constructor is very large, in other words the constructor has many parameters. He asked Mr. Y to solve that problem.
Huge Constructor / Too many parametersApproach 2
Again Mr. Y has started working on it. He has made some changes in the Pizza class. He has converted a variable into properties and removed the parameterized constructor.
- public class Pizza
- {
- public DoughType doughType {get; set;}
- public bool isRedPepper { get; set; }
- public Size size { get; set; }
- public CheeseType cheeseType { get; set; }
- public List<string> vegetables { get; set; }
-
- public void PizzaContent()
- {
- Console.WriteLine("Pizza with {0}", doughType);
- if (isRedPepper)
- Console.WriteLine("Red Pepper");
- Console.WriteLine("Size: {0}", size);
- Console.WriteLine("Cheese Type: {0}", cheeseType);
- Console.WriteLine("Vegetables:");
- foreach (var item in vegetables)
- {
- Console.WriteLine(" {0}", item);
- }
- }
- }
-
- public enum DoughType
- {
- Neapolitan_Pizza_Dough = 1,
- NewYorkStyle_Pizza_Dough = 2,
- SquarePan_Pizza_Dough = 3
- }
-
- public enum CheeseType
- {
- American = 1,
- Swiss = 2,
- }
-
- public enum Size
- {
- Small = 1,
- Medium = 2,
- Large = 3
- }
Client-Side Code
Mr. Y has created an object of the Pizza class and has set all the properties for that object and called the method.
- static void Main(string[] args)
- {
- var pizza = new Pizza();
- pizza.doughType = DoughType.Neapolitan_Pizza_Dough;
- pizza.isRedPepper = true;
- pizza.size = Size.Large;
- pizza.cheeseType = CheeseType.American;
- pizza.vegetables = new List<string> { "Tomato", "Corn" };
-
- pizza.PizzaContent();
-
- Console.ReadKey();
- }
Output
Mr. Y has built and run the application again.
This time Mr. Y thought that he has solved the problem. He has shown the application to his Project Manager Mr. X. Mr. X was a little happy but explained that the client needs to remember all the properties, so you need to figure out some solution.
Need to remember all the propertiesApproach 3
Mr. Y has again started working on that and he got the solution for not remembering the properties. This time he didn't change anything in the Pizza class.
- public class Pizza
- {
- public DoughType doughType {get; set;}
- public bool isRedPepper { get; set; }
- public Size size { get; set; }
- public CheeseType cheeseType { get; set; }
- public List<string> vegetables { get; set; }
-
- public void PizzaContent()
- {
- Console.WriteLine("Pizza with {0}", doughType);
- if (isRedPepper)
- Console.WriteLine("Red Pepper");
- Console.WriteLine("Size: {0}", size);
- Console.WriteLine("Cheese Type: {0}", cheeseType);
- Console.WriteLine("Vegetables:");
- foreach (var item in vegetables)
- {
- Console.WriteLine(" {0}", item);
- }
- }
- }
- public enum DoughType
- {
- Neapolitan_Pizza_Dough = 1,
- NewYorkStyle_Pizza_Dough = 2,
- SquarePan_Pizza_Dough = 3
- }
-
- public enum CheeseType
- {
- American = 1,
- Swiss = 2,
- }
-
- public enum Size
- {
- Small = 1,
- Medium = 2,
- Large = 3
- }
Mr. Y has created a new class "MyPizzaBuilder". In this class he has implemented the following three major things:
- Create one method "GetPizza" that returns an instance of a Pizza class
- Create methods ("PrepareDough", "AppyVegetables", "AppyCheese", "AddCondiments") to set properties
- Create one more method "CreatePizza" that initializes an object of the Pizza class and calls the preceding methods
- public class MyPizzaBuilder
- {
- Pizza pizza;
-
- public Pizza GetPizza()
- {
- return pizza;
- }
-
- public void CreatePizza()
- {
- pizza = new Pizza();
-
- PrepareDough();
- ApplyVegetables();
- ApplyCheese();
- AddCondiments();
- }
-
- private void AddCondiments()
- {
- pizza.isRedPepper = true;
- }
-
- private void ApplyCheese()
- {
- pizza.cheeseType = CheeseType.American;
- }
-
- private void ApplyVegetables()
- {
- pizza.vegetables = new List<string> { "Tomato", "Corn" };
- }
-
- private void PrepareDough()
- {
- pizza.doughType = DoughType.Neapolitan_Pizza_Dough;
- pizza.size = Size.Large;
- }
- }
Client-Side Code
In the client side Mr. Y has created an object of the "MyPizzaBuilder" class and called the "CreatePizza" method. As we have seen the "CreatePizza" method calls all the methods that are required to set the properties. So in this way the client does not need to remember all the properties.
- static void Main(string[] args)
- {
- var builder = new MyPizzaBuilder();
- builder.CreatePizza();
- var pizza = builder.GetPizza();
- pizza.PizzaContent();
-
- Console.ReadKey();
- }
Output
Again Mr. Y has built and run the application and was very happy. He just explained the solution to his Project Manager. The Project Manager appreciated his efforts and asked one question "What if we want to create another type of Pizza?" Mr. Y was realized that he must copy "MyPizzaBuilder" and then change the definition of creating a Pizza.
If you want to create another type of Pizza then you need to create another class and copy from MyPizzaBuilder and change the definition of creating a Pizza. This is not a good practice.
Approach 4
Mr. Y has become fed up with the situation and this time he just wants a concrete solution. So he has thought about it and came up with the solution. In this approach he also didn't change anything in the "Pizza" class.
- public class Pizza
- {
- public DoughType doughType {get; set;}
- public bool isRedPeeper { get; set; }
- public Size size { get; set; }
- public CheeseType cheeseType { get; set; }
- public List<string> vegetables { get; set; }
-
- public void PizzaContent()
- {
- Console.WriteLine("Pizza with {0}", doughType);
- if (isRedPeeper)
- Console.WriteLine("Red Peeper");
- Console.WriteLine("Size: {0}", size);
- Console.WriteLine("Cheese Type: {0}", cheeseType);
- Console.WriteLine("Vegetables:");
- foreach (var item in vegetables)
- {
- Console.WriteLine(" {0}", item);
- }
- }
- }
- public enum DoughType
- {
- Neapolitan_Pizza_Dough = 1,
- NewYorkStyle_Pizza_Dough = 2,
- SquarePan_Pizza_Dough = 3
- }
-
- public enum CheeseType
- {
- American = 1,
- Swiss = 2,
- }
-
- public enum Size
- {
- Small = 1,
- Medium = 2,
- Large = 3
- }
This time he has created an abstract class "PizzaBuilder" and declared all the methods that were used to set the properties as abstract.
- public abstract class PizzaBuilder
- {
- protected Pizza pizza;
-
- public Pizza GetPizza()
- {
- return pizza;
- }
-
- public void CreateNewPizza()
- {
- pizza = new Pizza();
- }
-
- public abstract void PrepareDough();
- public abstract void ApplyVegetables();
- public abstract void ApplyCheese();
- public abstract void AddCondiments();
- }
Mr. Y has inherited the preceding abstract class into "MyPizzaBuilder" and implemented all the abstract methods. So in this way he has a solution and implemented it. Now if the client wants to create another type of pizza then he must inherit the same abstract class and implement the properties.
- public class MyPizzaBuilder : PizzaBuilder
- {
- public override void AddCondiments()
- {
- pizza.isRedPeeper = true;
- }
-
- public override void ApplyCheese()
- {
- pizza.cheeseType = CheeseType.American;
- }
-
- public override void ApplyVegetables()
- {
- pizza.vegetables = new List<string> { "Tomato", "Corn" };
- }
-
- public override void PrepareDough()
- {
- pizza.doughType = DoughType.Neapolitan_Pizza_Dough;
- pizza.size = Size.Large;
- }
- }
Mr. Y has created one more class, "PizzaMaker", to construct an object using the builder abstract class. So in this class the client must pass a builder and the methods are called of that specific concrete builder.
- public class PizzaMaker
- {
- private readonly PizzaBuilder builder;
-
- public PizzaMaker(PizzaBuilder builder)
- {
- this.builder = builder;
- }
-
- public void BuildPizza()
- {
- builder.CreateNewPizza();
- builder.PrepareDough();
- builder.ApplyVegetables();
- builder.ApplyCheese();
- builder.AddCondiments();
- }
-
- public Pizza GetPizza()
- {
- return builder.GetPizza();
- }
- }
Let's check what Mr. Y has done so far using a Class Diagram. Mr. Y has created the "Pizza" class that was our product. Then he created a "PizzaBuilder" abstract class that was our builder. Then he has created two concrete builders, in other words "MyPizzaBuilder" and "TomatoPizzaBuilder". In the future the client can also add more concrete builders, in other words more types of Pizzas. Finally he has created the "PizzaMaker" class that was our director.
Participants
- Builder (PizzaBuilder): Specifies an abstract interface for creating parts of a Product object.
- ConcreteBuilder (MyPizzaBuilder, TomatoPizzaBuilder): Constructs and assembles parts of the product by implementing the Builder interface and defines and keeps track of the representation it creates. Provides an interface for retrieving the product.
- Director (PizzaMaker): Constructs an object using the Builder interface.
- Product (Pizza): Represents the complex object under construction. ConcreteBuilder builds the product's internal representation and defines the process by which it's assembled includes classes that define the constituent parts, including interfaces for assembling the parts into the final result.
Client-Side Code
In the client-side code Mr. Y has created an object of the "PizzaMaker" class and passed a "MyPizzaBuilder" object. Then he called the BuildPizza method that calls all the necessary methods to create a Pizza for that builder. In the third step he called the GetPizza method followed by the PizzaContent method. The same was done for "TomatoPizzaBuilder" also.
- static void Main(string[] args)
- {
- var pizzaMaker = new PizzaMaker(new MyPizzaBuilder());
- pizzaMaker.BuildPizza();
- var pizza1 = pizzaMaker.GetPizza();
-
- pizza1.PizzaContent();
-
- pizzaMaker = new PizzaMaker(new TomatoPizzaBuilder());
- pizzaMaker.BuildPizza();
- var pizza2 = pizzaMaker.GetPizza();
-
- pizza2.PizzaContent();
-
- Console.ReadKey();
- }
Output
Mr. Y has built and run the application and gave the demo to Mr. X.
This time Mr. X has gone through the application and was very happy as Mr Y. has implemented "The Builder Design Pattern".
Summary
When we have multiple parameters, the order of those parameters are important and when we have different constructions, we should need to separate the construction of an object from its representation.