Design Patterns

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.

Example 1: Builder Design Pattern for Sandwiches
Step 1: Define the Product.

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();
    }
}

Conclusion

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.