It boils down to never having a Foo in a half constructed state
Some reasons that spring to mind:
The builder's is by nature tied to the class it is constructing. You can think of the intermediate states of the builder as partial application of the constructor, so your argument that it is too coupled isn't persuasive.
By passing the whole builder in, fields in MyClass can be final, making reasoning about MyClass easier. If the fields are not final, you could just as easily have fluent setters than a builder.
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.
Best Answer
It's so you can be immutable AND simulate named parameters at the same time.
That keeps your mitts off the person until it's state is set and, once set, won't let you change it, yet every field is clearly labeled. You can't do this with just one class in Java.
It looks like you're talking about Josh Blochs Builder Pattern. This should not be confused with the Gang of Four Builder Pattern. These are different beasts. They both solve construction problems, but in fairly different ways.
Of course you can construct your object without using another class. But then you have to choose. You lose either the ability to simulate named parameters in languages that don't have them (like Java) or you lose the ability to remain immutable throughout the objects lifetime.
Immutable example, has no names for parameters
Here you're building everything with a single simple constructor. This will let you stay immutable but you loose the simulation of named parameters. That gets hard to read with many parameters. Computers don't care but it's hard on the humans.
Simulated named parameter example with traditional setters. Not immutable.
Here you're building everything with setters and are simulating named parameters but you're no longer immutable. Each use of a setter changes object state.
So what you get by adding the class is you can do both.
Validation can be performed in the
build()
if a runtime error for a missing age field is enough for you. You can upgrade that and enforce thatage()
is called with a compiler error. Just not with the Josh Bloch builder pattern.For that you need an internal Domain Specific Language (iDSL).
This lets you demand that they call
age()
andname()
before callingbuild()
. But you can't do it just by returningthis
each time. Each thing that returns returns a different thing that forces you to call the next thing.Use might look like this:
But this:
causes a compiler error because
age()
is only valid to call on the type returned byname()
.These iDSLs are extremely powerful (JOOQ or Java8 Streams for example) and are very nice to use, especially if you use an IDE with code completion, but they are a fair bit of work to set up. I'd recommend saving them for things that will have a fair bit of source code written against them.