9

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.

NingW
  • 269
  • 3
  • 12
  • 1
    Possible duplicate of [When should the builder design pattern be used?](http://softwareengineering.stackexchange.com/questions/341196/when-should-the-builder-design-pattern-be-used) – whatsisname Apr 06 '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 06 '17 at 19:34
  • Possible duplicate of [GoF's implementation of Builder in real life](http://softwareengineering.stackexchange.com/questions/330758/gofs-implementation-of-builder-in-real-life) – gnat Apr 06 '17 at 20:18

4 Answers4

29

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.

amon
  • 132,749
  • 27
  • 279
  • 375
4

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.

Frank Puffer
  • 6,411
  • 5
  • 21
  • 38
  • 2
    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 07 '17 at 12:12
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.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
  • 2
    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 06 '17 at 18:03
  • The builder pattern can still be useful in c#. http://softwareengineering.stackexchange.com/a/341229/6644 – whatsisname Apr 06 '17 at 18:03
  • @svidgen: Of course, but the same principle should hold for all of the constructors as well. That's the whole point of having constructors: to new up a valid object. – Robert Harvey Apr 06 '17 at 18:04
  • @RobertHarvey Sure. You could throw an exception in a constructor ... but, isn't that generally frowned upon? – svidgen Apr 06 '17 at 18:04
  • @svidgen: Oh, so you mean, for example, if an invariant like a numeric range in one of the parameters is violated? How else would you do it besides throwing an exception? You're not advocating a builder for every possible object instantiation, are you? – Robert Harvey Apr 06 '17 at 18:06
  • @whatsisname: I don't see how that post is relevant. You can still check object integrity in a C# constructor with optional parameters. – Robert Harvey Apr 06 '17 at 18:08
  • @RobertHarvey Hmm ... I'm not sure, now that you mention it! The builder pattern certainly gives you more options though. You could throw an exception -- or, if the parameters are satisfied by a subclass, you could generate return that. Or ... if you want to avoid exceptions for some reason, you could return `null` -- not an option for a constructor. ... Just seems like avoiding the litany of constructors isn't a main benefit; the crazy stuff you can do in the build step is. – svidgen Apr 06 '17 at 18:09
  • @svidgen: Of course, nowadays a lot of this work is probably done with DI containers and Code Contracts. The Builder Pattern does not substitute for those. – Robert Harvey Apr 06 '17 at 18:12
  • 1
    @RobertHarvey: what I linked is relevant because the Builder pattern is useful for more than just the telescoping parameter problem. – whatsisname Apr 06 '17 at 18:12
  • @svidgen: A `static`Factory method with optional parameters instead of a constructor solves the "return null" problem. – Robert Harvey Apr 06 '17 at 18:15
  • @whatsisname: The things you describe there can all be solved with a constructor having optional parameters. – Robert Harvey Apr 06 '17 at 18:17
  • 10
    -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 06 '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 06 '17 at 20:31
  • Thanks for your reply. Please consider re-read my question. – NingW Apr 07 '17 at 01:23
  • @N1ng: See amon's answer. – Robert Harvey Apr 07 '17 at 01:34
  • Optional parameters will make code more complicated with parameters which are some other type. Because default value for reference type can be only null. – Fabio Apr 07 '17 at 02:52
  • @amon: I think you are right, but I was astonished to find even the Wikipedia article telling the main purpose of the (GoF) Builder would be solving the "telescoping constructor" problem. Looks to my like an error in Wikipedia? – Doc Brown Apr 07 '17 at 05:44
  • @DocBrown it does solve it, but it isn't what make it different to Josh Bloch, it could help some clarification I guess – Walfrat Apr 07 '17 at 09:15
  • 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 07 '17 at 09:22
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)

svidgen
  • 13,414
  • 2
  • 34
  • 60
  • 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 06 '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 06 '17 at 18:45