Builder

Construction of complex objects by extracting the steps and centralizing the control of what happens at each step

Pizza shop. Need multiple overloads to take a pizza order. Well, not really because C# allows for default parameters, but still, the constructor method looks pretty intimidating even though these are not even all possible toppings.

namespace NoBuilderPattern
{
    using System;

    public class Pizza {
        private int size = 16;
        private bool cheese = true;
        private bool pepperoni = false;
        private bool blackOlives = false;
        private bool greenOlives = false;
        private bool bellPeppers = false;
        private bool pineapple = false;
        private bool onions = false;
        private bool steak = false;
        private bool chicken = false;
        private bool bacon = false;
        private bool ranch = false;

        public Pizza()
        {
            this.size = 16;
        }

        public Pizza(int size)
        {
            this.size = size;
        }

        public Pizza(bool pepperoni)
        {
            this.pepperoni = pepperoni;
        }

        public Pizza(int size = 16, bool cheese = true, bool pepperoni = false, 
            bool blackOlives = false, bool greenOlives = false, bool bellPeppers = false,
            bool pineapple = false, bool onions = false, bool steak = false,
            bool chicken = false, bool bacon = false, bool ranch = false)
        {
            this.size = size;
            this.cheese = cheese;
            this.pepperoni = pepperoni;
            this.blackOlives = blackOlives;
            this.greenOlives = greenOlives;
            this.bellPeppers = bellPeppers;
            this.pineapple = pineapple;
            this.onions = onions;
            this.steak = steak;
            this.chicken = chicken;
            this.bacon = bacon;
            this.ranch = ranch;
        }
    }
}
  

Problems that Builder pattern can help address:

  • Lots of computational logic in class constructors
  • Number of related classes that perform similar functions with different representations

Advantages that Builder brings along:

  • Control over the construction process
  • Lets vary internal product representation
  • Focuses on sequence of steps (Factory focuses on families of items being created)

Recipe:

First, extract the interface of methods that help create common parts for all the Products:

  interface PizzaBuilder {
    prepareDough(): void;
    addSauce(): void;
    addIngredients(): void;
    addCheese(): void;
    bake(): void;
  }
  
This particular class extracts the common methods from the children classes for simplicity. It's not necessarily a part of the pattern, but makes the code cleaner in this particular example.

  class PizzaBase {
    protected pizza: Pizza;

    constructor() {
      this.reset();
    }
    //this call ensures that the Product is started from scratch every time
    //The pattern is not strict about it, and this can be customized
    public reset(): void {
      this.pizza = new Pizza(); 
    }

    public getProduct(): Pizza {
      const result = this.pizza;
      this.reset();
      return result;
    }
  }
  
This is one specific implementation of a Product that requires certain steps.

  class CheesePizzaBuilderThinCrust16Inches
    extends PizzaBase
    implements PizzaBuilder
  {
    constructor() {
      super();
    }

    prepareDough(): void {
      this.pizza.parts.push("thin crust, 16 inches");
    }
    addSauce(): void {
      this.pizza.parts.push("marinara sauce");
    }
    addIngredients(): void {} //no extra ingredients

    addCheese(): void {
      this.pizza.parts.push("mozzarella, 1x");
    }
    bake(): void {
      this.pizza.parts.push("bake now");
    }
  }
  
Another Builder just to show the possibilities.

  class ChicagoDeepDish16InchSupreme extends PizzaBase implements PizzaBuilder {
    constructor() {
      super();
    }

    prepareDough(): void {
      this.pizza.parts.push("deep dish crust, 16 inches");
    }
    addSauce(): void {
      this.pizza.parts.push("marinara sauce");
    }
    addIngredients(): void {
      this.pizza.parts.push("black olives");
      this.pizza.parts.push("bell peppers");
      this.pizza.parts.push("green olives");
      this.pizza.parts.push("red onions");
      this.pizza.parts.push("italian sausage");
      this.pizza.parts.push("kitchen sink");
    }

    addCheese(): void {
      this.pizza.parts.push("mozzarella, 1x");
    }
    bake(): void {
      this.pizza.parts.push("bake now");
    }
  }
  
In this scenarios, both Builders return objects of the same type: Pizza. But, unlike in other creational patterns, Builder pattern can return objects of different, unrelated types.

  class Pizza {
    public parts: string[] = [];

    public listParts(): void {
      console.log(`Product parts: ${this.parts.join(", ")}\n`);
    }
  }
  
The Director class is actually optional, as it is only used to control the order of events. One Director can construct several product variations by altering the order of steps. Also, Builders can be controlled directly for even finer customizations.

  class Director {
    private builder: PizzaBuilder;

    public setBuilder(builder: PizzaBuilder): void {
      this.builder = builder;
    }

    public prepForTakeOutAndBakeAtHome(): void {
      this.builder.prepareDough();
      this.builder.addSauce();
      this.builder.addIngredients();
      this.builder.addCheese();
    }

    public prepAndBake(): void {
      this.builder.prepareDough();
      this.builder.addSauce();
      this.builder.addIngredients();
      this.builder.addCheese();
      this.builder.bake();
    }
  }
  
Testing code

  function clientCode(director: Director) {
    const builder = new CheesePizzaBuilderThinCrust16Inches();
    director.setBuilder(builder);

    console.log("Kind of like Papa Murphy's:");
    director.prepForTakeOutAndBakeAtHome();
    builder.getProduct().listParts();

    console.log("For here:");
    director.prepAndBake();
    builder.getProduct().listParts();

    // Remember, the Builder pattern can be used without a Director class.
    console.log("Extra cheesy");
    builder.prepareDough();
    builder.addSauce();
    builder.addIngredients();
    builder.addCheese();
    builder.addCheese();
    builder.addCheese();
    builder.bake();
    builder.getProduct().listParts();

    const builder2 = new ChicagoDeepDish16InchSupreme();
    director.setBuilder(builder2);
    console.log("Chicago deep dish to go");
    director.prepForTakeOutAndBakeAtHome();
    builder2.getProduct().listParts();

    console.log("Chicago deep dish for here");
    director.prepAndBake();
    builder2.getProduct().listParts();
  }

  const director = new Director();
  clientCode(director);
Resulting output:

        Kind of like Papa Murphy's:
        Product parts: thin crust, 16 inches, marinara sauce, mozzarella, 1x

        For here:
        Product parts: thin crust, 16 inches, marinara sauce, mozzarella, 1x, bake now

        Extra cheesy
        Product parts: thin crust, 16 inches, marinara sauce, mozzarella, 1x, mozzarella, 1x, mozzarella, 1x, bake now

        Chicago deep dish to go
        Product parts: deep dish crust, 16 inches, marinara sauce, black olives, bell peppers, green olives, red onions, italian sausage, kitchen sink, mozzarella, 1x

        Chicago deep dish for here
        Product parts: deep dish crust, 16 inches, marinara sauce, black olives, bell peppers, green olives, red onions, italian sausage, kitchen sink, mozzarella, 1x, bake now