Let's dive into a detailed example of the Builder design pattern in C#. In this example, we'll create a system for building different types of sandwiches using the Builder pattern.
First, let's define the Sandwich class which represents the complex object we want to build:
// Product: Sandwich
public class Sandwich
{
public string Bread { get; set; }
public string Meat { get; set; }
public string Cheese { get; set; }
public List Vegetables { get; set; }
public List Sauces { get; set; }
public Sandwich()
{
Vegetables = new List();
Sauces = new List();
}
public void Display()
{
Console.WriteLine($"Sandwich with {Bread} bread:");
Console.WriteLine($"Meat: {Meat}");
Console.WriteLine($"Cheese: {Cheese}");
Console.WriteLine($"Vegetables: {string.Join(", ", Vegetables)}");
Console.WriteLine($"Sauces: {string.Join(", ", Sauces)}");
Console.WriteLine();
}
}
Step 2: Define the Builder Interface
Next, create an interface ISandwichBuilder that defines methods for building different parts of the sandwich:
// Builder Interface: ISandwichBuilder
public interface ISandwichBuilder
{
void BuildBread();
void BuildMeat();
void BuildCheese();
void BuildVegetables();
void BuildSauces();
Sandwich GetSandwich();
}
Step 3: Implement Concrete Builders.
Implement concrete builders (VegetarianSandwichBuilder and ClubSandwichBuilder) that implement the ISandwichBuilder interface to build specific types of sandwiches:
Concrete Builder: Vegetarian Sandwich Builder
// Concrete Builder: VegetarianSandwichBuilder
public class VegetarianSandwichBuilder : ISandwichBuilder
{
private Sandwich _sandwich;
public VegetarianSandwichBuilder()
{
_sandwich = new Sandwich();
}
public void BuildBread()
{
_sandwich.Bread = "Whole grain bread";
}
public void BuildMeat()
{
// Vegetarian sandwich does not contain meat
_sandwich.Meat = "None";
}
public void BuildCheese()
{
_sandwich.Cheese = "Swiss cheese";
}
public void BuildVegetables()
{
_sandwich.Vegetables.AddRange(new string[] { "Lettuce", "Tomato", "Cucumber" });
}
public void BuildSauces()
{
_sandwich.Sauces.Add("Mayonnaise");
}
public Sandwich GetSandwich()
{
return _sandwich;
}
}
Concrete Builder: Club Sandwich Builder
// Concrete Builder: ClubSandwichBuilder
public class ClubSandwichBuilder : ISandwichBuilder
{
private Sandwich _sandwich;
public ClubSandwichBuilder()
{
_sandwich = new Sandwich();
}
public void BuildBread()
{
_sandwich.Bread = "Sourdough bread";
}
public void BuildMeat()
{
_sandwich.Meat = "Turkey";
}
public void BuildCheese()
{
_sandwich.Cheese = "Cheddar cheese";
}
public void BuildVegetables()
{
_sandwich.Vegetables.AddRange(new string[] { "Lettuce", "Tomato", "Bacon" });
}
public void BuildSauces()
{
_sandwich.Sauces.Add("Mustard");
}
public Sandwich GetSandwich()
{
return _sandwich;
}
}
Step 4: Create a Director (Optional).
Although optional, a SandwichMaker class can act as a director to control the assembly process of building a sandwich:
// Director: SandwichMaker (optional)
public class SandwichMaker
{
private ISandwichBuilder _builder;
public SandwichMaker(ISandwichBuilder builder)
{
_builder = builder;
}
public void MakeSandwich()
{
_builder.BuildBread();
_builder.BuildMeat();
_builder.BuildCheese();
_builder.BuildVegetables();
_builder.BuildSauces();
}
public Sandwich GetSandwich()
{
return _builder.GetSandwich();
}
}
Step 5: Client Usage
Now, let's see how we can use these components to construct different types of sandwiches:
// Client code
class Program
{
static void Main(string[] args)
{
// Create a sandwich maker with a specific builder
ISandwichBuilder builder;
// Create a vegetarian sandwich
builder = new VegetarianSandwichBuilder();
SandwichMaker vegetarianSandwichMaker = new SandwichMaker(builder);
vegetarianSandwichMaker.MakeSandwich();
Sandwich vegetarianSandwich = vegetarianSandwichMaker.GetSandwich();
Console.WriteLine("Vegetarian Sandwich:");
vegetarianSandwich.Display();
// Create a club sandwich
builder = new ClubSandwichBuilder();
SandwichMaker clubSandwichMaker = new SandwichMaker(builder);
clubSandwichMaker.MakeSandwich();
Sandwich clubSandwich = clubSandwichMaker.GetSandwich();
Console.WriteLine("Club Sandwich:");
clubSandwich.Display();
}
}
The Builder pattern is particularly useful when the creation of objects involves intricate steps or configurations, and when there is a need to create different variations of the same object structure.