Design Patterns – Advantages of the Builder Pattern

design-patterns

Update: Without fluent interface, builder pattern can still be done, see my implementation.

Edit for possible duplication issues:

  • When should the builder design pattern be used?: My question is about the actual advantages of Builder Pattern(of GoF). And the chosen answer in the link is about Bloch Builder, which may(See @amon 's answer) or may not be a pattern.

    Design Patterns are NOT for solving a specific problem(telescoping constructor or so). See reference.

    So, what make something be a pattern? (The LHS are what pointed out by John Vlissides. The RHS my opinion.)

    1. Recurrence. (A pattern should be general, so it can be applied to many problems.)

    2. Teaching. (It should let me know how to improve my current solution scenario.)

    3. It has a name. (For more effective conversation.)

    Reference: Pattern Hatching: Design Patterns Applied written by John Vlissides. Chapter one: common mis-understandings about design patterns, if I remember.

  • GoF's implementation of Builder in real life: You can read this answer before reading my notes about builder pattern. It's a great answer, but still, it doesn't solve my questions. And the title is not related.


UML of Builder Pattern:

UML of Builder Pattern of GoF

Reference design-patterns-stories.com


I've read the book of GoF about builder pattern again and again, the followings are some of my notes:

  • Director :

    • A director can be reused.
    • And it contains the algorithm(s) to build a product, step by step.
    • Use the interface provided by Builder.
    • Logically, it create the product.
  • Builder :

    • A builder should be general enough (to let almost any possible different ConcreteBuilder to build their corresponding product. This is the origin of my second question below.)
  • ConcreteBuilder :

    • A concrete builder builds all the parts needed, and know how to assemble them.
    • And keeps track of its product. (It contains a reference of its product.)
    • Client get their final product from a concrete builder. (It's ConcreteBuilder who has the getProduct() method, Builder don't have getProduct() (abstract) method.)
  • Product :

    • It's the complex object to be built. For every ConcreteBuilder, there is a corresponding Product. (This is the origin of my first question below.)
    • And it provide the interface for its corresponding concrete builder to build the logical parts and to assemble them.

    This is why people confused about Bloch builder and builder pattern of GoF. Bloch builder just makes the interface easier to be used by the concrete builder. (Btw, how to indent this line…)

  • The client :

    • Choose a concrete builder he needs. (Implementor)
    • And choose a director he needs. (Logic)
    • Then inject the concrete builder into the director.
    • Call director's construct() method.
    • Get the product by calling getProduct() of concrete builder.

It takes me a lot of effort to remember all these rules, but I have some questions:

First:

If the Product is complex enough, it should be a bad design.

Second:

Ok, if you say it's not a bad design. So how can you design the Builder interface to satisfy any ConcreteProduct?

The followings are advantages of the Builder Pattern for me:

  • The Scope: A ConcreteBuilder constrains all components it needs in the same scope. So the client of the Builder don't see anything about/related to Product.

  • Less Parameters in Builder: Since all the methods inside the ConcreteBuilder can share those variables, the methods of ConcreteBuilder should be easier to read and write. (From the book Clean Code, the more parameters a method has, the worse.)

  • Dependency Inversion Principle: The Builder interface plays a key role in the builder pattern. Why? Because now both Director(the logic) and ConcreteBuilder(the implementation) follow the same rule to build a thing.

After all, I'm not sure. I need the actual answer.


I appreciate any different perspectives about what is a builder pattern. Different people will have different definition of their own, but here I'm talking about Builder Pattern of GoF. So please read carefully.

Days before, I followed the answer of @Aaron in When would you use the Builder Pattern? [closed], and thought that was a builder pattern of GoF. Then I post my implementation practice at CodeReview.

But people there pointed out that it's not a Builder Pattern. Like @Robert Harvey, I disagreed about it. So I come here for the real answer.

Best Answer

The GoF Builder pattern is one of the less important patterns suggested in the Design Patterns book. I haven't seen applications of the pure GoF Builder pattern outside of parsers, document converts, or compilers. A builder-like API is often used to assemble immutable objects, but that ignores the flexibility of interchangeable concrete builders that the GoF envisioned.

The name “Builder Pattern” is now more commonly associated with Joshua Bloch's Builder Pattern, which intends to avoid the problem of too many constructor parameters. If not only applied to constructors but to other methods, this is technique also known as the Method Object Pattern.

The Go4 Builder Pattern is applicable when:

  • the object cannot be constructed in one call. This might be the case when the product is immutable, or contains complex object graphs e.g. with circular references.

  • you have one build process that should build many different kinds of products.

The latter point is the key to the GoF builder pattern: our code isn't restricted to a specific product, but operates in terms of an abstract builder strategy. An UML diagram for a Builder is very similar to the Strategy Pattern, with the difference that the Builder accumulates data until it creates an object through a non-abstract method. An example might help.

The introductory example for the Builder Pattern in the Design Patterns book is a text converter or document builder. A text document (in their model) contains text, the text can have different fonts, and text can be separated by paragraph breaks. An abstract builder provides functions for these. A user (or “director”) can then use this abstract builder interface to assemble the document. E.g. this text

Lorem ipsum.

Dolor sit?

Amet.

Might be created like

void CreateExampleText(ITextBuilder b)
{
  b.SwitchFont(Font.DEFAULT);
  b.AddText("Lorem ipsum.");
  b.AddParagraphBreak();

  b.SwitchFont(Font.BOLD);
  b.AddText("Dolor sit?");
  b.SwitchFont(Font.DEFAULT);
  b.AddParagraphBreak();

  b.SwitchFont(Font.ITALIC);
  b.AddText("Amet.");
}

Now the notable thing is that the CreateExampleText() function does not depend on any specific builder, and does not assume any particular document type. We can therefore create concrete builders for plain text, HTML, TeX, Markdown, or any other format. The plain text builder would of course not be able to represent different fonts, so that method would be empty:

interface ITextBuilder {
  void SwitchFont(Font f);
  void AddText(string s);
  void AddParagraphBreak();
}

class PlainTextBuilder : ITextBuilder
{
  private StringBuilder sb = new StringBuilder();

  void SwitchFont(Font f) { /* not possible in plain text */ }
  void AddText(string s) { sb.Append(s); }
  void AddParagraphBreak() { sb.AppendLine(); sb.AppendLine(); }

  string GetPlainText() { return sb.ToString(); }
}

We can then use this director function and the concrete builder like

var builder = new PlainTextBuilder();
CreateExampleText(builder);
var document = builder.GetPlainText();

Note that the StringBuilder itself is also seems to be a builder of this kind: the complete string (= product) is assembled piece by piece by user code. However, a C# StringBuilder is not polymorphic: There is no abstract builder, but only one concrete builder. As such, it doesn't quite fit the GoF design pattern. I discuss this in more detail in my answer to Is “StringBuilder” an application of the Builder Design Pattern?

A notable real-world example of the Builder Pattern is the ContentHandler interface in the SAX streaming XML parser. This parser does not build a document model itself, but calls back into a user-provided content handler. This avoids the cost of building a complete document model when the user only needs a simpler data representation. While the user provided content handler is not necessarily a builder, it allows users to inject a document builder of their design into the SAX parser.