The builder pattern is a creational design pattern. It is used when the creation of a product involves multiple, independent steps.

What's the one thing that immediately comes to your mind when you think of something that requires multiple steps to be "built"? That's right, subway!! 🥙🤤 You choose your bread, you choose if you want cheese, you choose your toppings and finally your sauces!

I'm sure you understand the process of creating a sub. Let's see what it would look like if we coded it using the Builder pattern:

Subway

Let us assume that we have 2 different types(recipes) for a sub - Veggie Delight and Chicken Teriyaki. Each of these would have a "Builder" dedicated to them.

The interface that these builders implement will be the same - That way, we can easily add more types(recipes) to our menu.

type sub struct {
    bread string
    hasCheese bool
    toppings []string
    sauces []string
}
type iSubBuilder interface {
    setBread()
    setCheese()
    setToppings()
    setSauces()
    getSub() sub
}
type veggieDelightBuilder struct {
    sub
}

func (v *veggieDelightBuilder) setBread() {
    v.sub.bread = "parmesan oregano"
}

func (v *veggieDelightBuilder) setCheese() {
    v.sub.hasCheese = false
}

func (v *veggieDelightBuilder) setToppings() {
    v.sub.toppings = []string{"olives", "tomatoes", "onions", "jalapeños"}
}

func (v *veggieDelightBuilder) setSauces() {
    v.sub.sauces = []string{"south west"}
}

func (v *veggieDelightBuilder) getSub() sub {
    return v.sub
}
type chickenTeriyakiBuilder struct {
    sub
}

func (c *chickenTeriyakiBuilder) setBread() {
    c.sub.bread = "italian"
}

func (c *chickenTeriyakiBuilder) setCheese() {
    c.sub.hasCheese = true
}

func (c *chickenTeriyakiBuilder) setToppings() {
    c.sub.toppings = []string{"roasted chicken", "olives", "onions", "jalapeños"}
}

func (c *chickenTeriyakiBuilder) setSauces() {
    c.sub.sauces = []string{"chilli", "bbq"}
}

func (c *chickenTeriyakiBuilder) getSub() sub {
    return c.sub
}

We've successfully created the sub and the builders. As you may have observed, we can easily create another recipe and have it implement all the functions.

Now, let's create an optional director struct that would accept accept a builder and then build subs for us.

The director is not always part of the builder pattern. You could look at it as an added layer of abstraction for cleaner code

type director struct {
    builder iSubBuilder
}

func (d *director) setBuilder(builder iSubBuilder) {
    d.builder = builder
}

func (d *director) buildSub() sub {
    d.builder.setBread()
    d.builder.setCheese()
    d.builder.setToppings()
    d.builder.setSauces()

    return d.builder.getSub()
}

Aaand we're done 🎉

Let's see it in action:

func describeSub(sub sub) {
    fmt.Printf("bread: %s, cheese: %t, toppings: %s, sauces: %s\n", sub.bread, sub.hasCheese, strings.Join(sub.toppings, ", "), strings.Join(sub.sauces, ", "))
}

func main() {
    veggieDelight := &veggieDelightBuilder{}
    director := &director {
        builder: veggieDelight,
    }
    veggieDelightSub := director.buildSub()
    describeSub(veggieDelightSub)

    fmt.Println("------------")

    director.setBuilder(&chickenTeriyakiBuilder{})
    chickenTeriyakiSub := director.buildSub()

    describeSub(chickenTeriyakiSub)
}

Running this program should get you this in your terminal

bread: parmesan oregano, cheese: false, toppings: olives, tomatoes, onions, jalapeños, sauces: south west
------------
bread: italian, cheese: true, toppings: roasted chicken, olives, onions, jalapeños, sauces: chilli, bbq

You can find all the code for this tutorial on this github repo

Hope this made understanding the Builder pattern easier 🚀

Cheers ☕️