The Builder pattern separates the construction of an object from its representation so that the same construction process can create different representations.

The general idea is that the order in which things happen when an object is instantiated will be the same, but the actual details of those steps change based upon what the concrete implementation is.

NOTE: This post is part of a series demonstrating software design patterns using C# and .NET.  The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in the series!

The Rundown

  • Type: Creational
  • Useful?: 1/5 (Almost certainly not)
  • Good For: Creating objects which need several steps to happen in order, but the steps are different for different specific implementations.
  • Example Code: On GitHub
exceptionnotfound/DesignPatterns
Repository for all of my Daily Design Pattern posts. - exceptionnotfound/DesignPatterns
Project for this post: Builder

The Participants

  • The Builder specifies an abstract interface for creating parts of a Product.
  • The ConcreteBuilder constructs and assembles parts of the product by implementing the Builder interface. It must also define and track the representation it creates.
  • The Product represents the object being constructed. It includes classes for defining the parts of the object, including any interfaces for assembling the parts into the final result.
  • The Director constructs an object using the Builder interface.

A Delicious Example

To demonstrate how the Builder design pattern works, we once again turn our hungry eyes to that most portable and simple of lunch foods: the humble sandwich.

A turkey sandwich, served at a deli.

Here's the thing about sandwiches: the only thing that defines a sandwich is something edible between two slices of bread. That's it. This means that a hot dog is a sandwich, which seems like a ridiculous statement but is technically correct (and technically correct is the best kind of correct).

That said, different types of sandwiches require different steps in order to make them, but they're still just sandwiches. Most of the time, the same kinds of ingredients will be used to create many different kinds of sandwiches. Let's see how we can use the Builder pattern to build us some of these yummy sandwiches.

To start off, we need tom implement the Director participant. We'll call our Director class AssemblyLine, make it a class, and it will define in what steps the process of making a sandwich are called.

/// <summary>
/// The Director
/// </summary>
class AssemblyLine
{
    // Builder uses a complex series of steps
    // 
    public void Assemble(SandwichBuilder sandwichBuilder)
    {
        sandwichBuilder.AddBread();
        sandwichBuilder.AddMeats();
        sandwichBuilder.AddCheese();
        sandwichBuilder.AddVeggies();
        sandwichBuilder.AddCondiments();
    }
}

We also need to define the Product participant which is being built by the Builder participant. For this demo, the Product is, of course, a Sandwich.

/// <summary>
/// The Product class
/// </summary>
class Sandwich
{
    private string _sandwichType;
    private Dictionary<string, string> _ingredients 
        = new Dictionary<string, string>();

    // Constructor
    public Sandwich(string sandwichType)
    {
        this._sandwichType = sandwichType;
    }

    // Indexer
    public string this[string key]
    {
        get { return _ingredients[key]; }
        set { _ingredients[key] = value; }
    }

    public void Show()
    {
        Console.WriteLine("\n---------------------------");
        Console.WriteLine("Sandwich: {0}", _sandwichType);
        Console.WriteLine(" Bread: {0}", _ingredients["bread"]);
        Console.WriteLine(" Meat: {0}", _ingredients["meat"]);
        Console.WriteLine(" Cheese: {0}", _ingredients["cheese"]);
        Console.WriteLine(" Veggies: {0}", _ingredients["veggies"]);
        Console.WriteLine(" Condiments: {0}", _ingredients["condiments"]);
    }
}

Now that we know the definition of the product we are building, let's now create the Builder participant - an abstract class named SandwichBuilder:

/// <summary>
/// The Builder abstract class
/// </summary>
abstract class SandwichBuilder
{
    protected Sandwich sandwich;

    // Gets sandwich instance
    public Sandwich Sandwich
    {
        get { return sandwich; }
    }

    // Abstract build methods
    public abstract void AddBread();
    public abstract void AddMeats();
    public abstract void AddCheese();
    public abstract void AddVeggies();
    public abstract void AddCondiments();
}

Notice the five abstract methods. Each subclass of SandwichBuilder will need to implement those methods in order to properly build a sandwich.

Next, let's implement a few ConcreteBuilder classes to build some specific sandwiches.

/// <summary>
/// A ConcreteBuilder class
/// </summary>
class TurkeyClub : SandwichBuilder
{
    public TurkeyClub()
    {
        sandwich = new Sandwich("Turkey Club");
    }

    public override void AddBread()
    {
        sandwich["bread"] = "12-Grain";
    }

    public override void AddMeats()
    {
        sandwich["meat"] = "Turkey";
    }

    public override void AddCheese()
    {
        sandwich["cheese"] = "Swiss";
    }

    public override void AddVeggies()
    {
        sandwich["veggies"] = "Lettuce, Tomato";
    }

    public override void AddCondiments()
    {
        sandwich["condiments"] = "Mayo";
    }
}


/// <summary>
/// A ConcreteBuilder class
/// </summary>
class BLT : SandwichBuilder
{
    public BLT()
    {
        sandwich = new Sandwich("BLT");
    }

    public override void AddBread()
    {
        sandwich["bread"] = "Wheat";
    }

    public override void AddMeats()
    {
        sandwich["meat"] = "Bacon";
    }

    public override void AddCheese()
    {
        sandwich["cheese"] = "None";
    }

    public override void AddVeggies()
    {
        sandwich["veggies"] = "Lettuce, Tomato";
    }

    public override void AddCondiments()
    {
        sandwich["condiments"] = "Mayo, Mustard";
    }
}

/// <summary>
/// A ConcreteBuilder class
/// </summary>
class HamAndCheese : SandwichBuilder
{
    public HamAndCheese()
    {
        sandwich = new Sandwich("Ham and Cheese");
    }

    public override void AddBread()
    {
        sandwich["bread"] = "White";
    }

    public override void AddMeats()
    {
        sandwich["meat"] = "Ham";
    }

    public override void AddCheese()
    {
        sandwich["cheese"] = "American";
    }

    public override void AddVeggies()
    {
        sandwich["veggies"] = "None";
    }

    public override void AddCondiments()
    {
        sandwich["condiments"] = "Mayo";
    }
}

Once we have all the ConcreteBuilder classes written up, we can use them in our Main() like so:

static void Main(string[] args)
{
    SandwichBuilder builder;

    // Create shop with sandwich assembly line
    AssemblyLine shop = new AssemblyLine();

    // Construct and display sandwiches
    builder = new HamAndCheese();
    shop.Assemble(builder);
    builder.Sandwich.Show();

    builder = new BLT();
    shop.Assemble(builder);
    builder.Sandwich.Show();

    builder = new TurkeyClub();
    shop.Assemble(builder);
    builder.Sandwich.Show();

    // Wait for user
    Console.ReadKey();
}

The nice thing about this pattern is that we can now reuse the AssemblyLine class on any SandwichBuilder we wish, and we have more fine-grained control over how the sandwiches are built.

Yes, that pun was intentional.  No, I'm not sorry.

Will I Ever Use This Pattern?

Probably not. Let's face it, this is a lot of work to build these supposedly related items in a reusable manner. The patterns some degree of assumptions about how these objects should be created, and for me it's too many assumptions to rely on using this pattern in common projects. Seems to me like the Builder pattern has some uses, just not a lot of them.

Summary

The Builder pattern allows us to build related sets of objects with the same steps, but leaving the implementation of those steps up to the subclasses. It's not terribly useful as far as I can see, but if someone out there has a good use for it I'd love to hear about it in the comments.

Happy Coding!