There are two interpretations of the builder pattern. There is the Gang of four (GOF)1 definition which to me resembles a lot like an inverted strategy pattern, and it is concerned with separating the procedure of building a complex object from the representation of its parts. Then there is what I call the fluent builder pattern which is really trying to solve the constructor with many parameter problem.

Gang Of Four interpretation/definition

The motivating example given in the book is an odd one if you’re expecting a fluent style builder pattern.

It talks about an RTFReader that should be able to convert RTF to other formats(the complex object). The solution proposed is to have RTFReader(director) take in a TextConverter(builder) and have the RTFReader focus only on the parsing logic, leaving the representing format to the TextConverters.

Note here that there is a director component, the builder can be called multiple times, and the goal here is to produce the complex object. All of which are quite odd to talk about in the context of managing constructor parameters.

func main() {
    d := rtfReader(plainTextConverter())
    // alternatively
    // d := rtfReader(texTextConverter())
    res := d.parseRTF(rtfDoc)
}

type rtfReader struct {
    c TextConverter // Note that this is an interface.
}

func (d rtfReader) parseRTF(doc rtfDoc) Output {
    for {
        nextToken := parse(doc)
        //...
        output = d.c.Convert(nextToken)
        // ...
    }
}

The best way to remember this is to think of stringBuilder. You (director) are trying to build a string (the complex object) by telling it what and how (algorithm) it should join strings.

The fluent builder interpretation

In this interpretation23 the motivating problem is how to scale growing constructor parameters, when some or all possible parameters need to be specified. Proponents of this pattern would argue that even when all n paramters are required, having them in this form is more readable than having a function that takes n arguments.

func main() {
    c := newCarBuilder().withWheels(4).withColour("blue").build()
}

Note that in this case, there is no director to speak of, the builder is only used once - we can forget about the builder once we call build(), and the final object is most likely not that complex.

Reconciliation

In the strictest sense, one could argue that in the fluent style example, c could be complex if the parameters interact in some convoluted way. The director executes the straightforward algorithm of chaining the methods calls together and there is only one builder implementation: newCarBuilder.

The following form might make it more explicit what the director is.

func director(b carBuilder) car {
    return b.withWheels(4).withColour("blue").build()
}

Some of the examples on wikipedia4 have this form and hence don’t quite disambiguate the term, and that further muddies the water.

Conclusion

The term builder pattern has two forms discussed above and hence is ambiguous. Before I read the texts more carefully to prepare this post, I did not know the first pattern (which I use so often) as the builder pattern; it was more of an unnamed dependency injection pattern in my mind. I believe most people are in the same boat and would probably think of the second form when using the term. I don’t know if people actually use this term to refer to the pattern anymore.