5

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.

  • 1
    Possible duplicate of When should the builder design pattern be used? – whatsisname Apr 6 '17 at 18:02
  • 1
    complex != complicated. A complicated object should be refactored, but a complex object is just put together from many parts. The GoF Builder makes it easier to put these many parts together because you can add one part after another, instead of having to collect all parts yourself and then passing them to a constructor in one go. – amon Apr 6 '17 at 19:34
  • Possible duplicate of GoF's implementation of Builder in real life – gnat Apr 6 '17 at 20:18
23

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.

  • Thank you very much for your detailed, accurate, informative answer. Your effort is already in my memory. – Niing Jun 2 '18 at 14:56
3

The Builder Pattern solves a very specific problem: Telescoping Constructors.

  public Food(int id, String name) {
    this(id, name, 0, 0, 0);
  }

  public Food(int id, String name, int calories) {
    this(id, name, calories, 0, 0);
  }

  public Food(int id, String name, int servingSize) {
    this(id, name, 0, servingSize, 0);
  }

  public Food(int id, String name, int fat) {
    this(id, name, 0, 0, fat);
  }

  public Food(int id, String name, int calories, int servingSize) {
    this(id, name, calories, servingSize, 0);
  }
  public Food(int id, String name, int calories, int fat) {
    this(id, name, calories, 0, fat);
  }

  public Food(int id, String name, int servingSize, int fat) {
    this(id, name, 0, servingSize, fat);
  }

  public Food(int id, String name, int calories, int servingSize, int fat) {
    this(id, name, calories, servingSize, fat);
  }

If you use the Builder Pattern instead, you can simply:

Food food = new FoodBuilder().SetName("Bananas").SetCalories(120).Build();

This may not seem like much of an improvement over constructors, but if you have eight optional parameters and you want to represent every useful combination, you'll need 256 constructors.

You will also notice that the constructors example shown above won't actually work, since servingSize and fat have the same type, and so there's no way to properly represent the method polymorphism required.

In C#, the builder pattern is not required, because C# supports optional parameters with default values in constructors, so only one constructor is required:

public Food(int id, string name, int calories=0, int servingSize=0, int fat=0)

The parameters having a default value assigned to them are optional.

  • 1
    Isn't the most important part of the pattern the validation it promises though? ... I mean, in addition to eliminating the need for N constructors, it lets you construct an object, over time if you like, without any risk of building an invalid object. Because, you have a .Build() step, you can totally avoid creating Food, and instead throw exception, if any parameters are invalid or missing. – svidgen Apr 6 '17 at 18:03
  • 1
    @RobertHarvey: what I linked is relevant because the Builder pattern is useful for more than just the telescoping parameter problem. – whatsisname Apr 6 '17 at 18:12
  • 6
    -1: Your answer focusses on Joshua Bloch's Builder pattern which solves the “telescoping constructor” problem, but that only shares the name and superficial usage with the GoF Builder pattern mentioned in the question. The Design Patterns book focusses on polymorphic builders and on multi-step initialization of complex object graphs (i.e. design issues), not on the coding inconvenience of large function signatures. – amon Apr 6 '17 at 18:26
  • 2
    @amon Yeah it's unfortunate they ended up with the same name. I don't know which builder anyone is talking about unless they mention which book they got it from. GoF builder and Josh Bloch builder are different animals. – candied_orange Apr 6 '17 at 20:31
  • 2
    @Walfrat: Wikipedia currently says "the intention of the builder pattern is to find a solution to the telescoping constructor" - IMHO it should be "one application of the builder pattern is to find a solution to the telescoping constructor" instead. – Doc Brown Apr 7 '17 at 9:22
3

The main advantage of the Builder Pattern:

Whenever I am tempted to use it, it reminds me that my class is probably not well-designed because it has too many attributes, especially optional ones, and should be split up.

A class with many optional attributes shouldn't be a class in the first place because it doesn't represent a well-defined entity. It is better implemented as a map, a set or some other container.

  • 1
    I appreciate this sentiment, however a map of optional properties doesn't allow one to reason as well about those properties in code. I suppose you can package several properties into unique sets and treat this set as a single property of the class though. The other alternative is that you start abstracting your classes out further and further until they are needlessly complex and verbose, which violates KISS. – maple_shaft Apr 7 '17 at 12:12
0

In my understanding, the biggest value is the flexibility you get from the Build() step.

You can do three things with that Build() that you can't with a constructor in most (many?) languages:

  • Return null
  • Return a different type (but hopefully a subtype!)
  • Collect parameters early, but defer instance construction

And, you can do one thing that I've heard is frowned upon for constructors to do in some cases1:

  • Throw exceptions

As Robert's answer notes, you can also spare yourself an exponential litany of constructors in some languages. But, I'd still argue that the biggest value is in the flexibility afforded by the Build() step.

And, part of that benefit, particularly of deferring construction, is the ability to inject your builder into "configurators" of sorts.

(I'm struggling to recall a situation in which I've actually leveraged this ... but, I'll update this when/if I find it!)


1. "Your constructor should never throw a fatal exception on its own, but code it executes may cause a fatal exception." (https://stackoverflow.com/a/77797/779572)

  • 1
    Throwing an exception is not necessarily frowned upon for constructors, and in many paradigms it is the expected thing to do, such as in RAII. – whatsisname Apr 6 '17 at 18:16
  • 1
    @whatsisname Oh, I don't disagree. But, in general, I agree with some folks on the interwebs that constructors shouldn't... let's say, they shouldn't be fussy. Object construction seems to have more of an implied "easiness" about it. I don't normally expect new to fail if I've supplied all of the parameters. A Build(), on the other hand, suggests much more strongly to me, that "this will fail if you've given me bad materials." ... It's not a hard and fast rule, but it's a general expectation. – svidgen Apr 6 '17 at 18:45

protected by gnat Nov 28 '18 at 21:15

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.