It's so you can be immutable AND simulate named parameters at the same time.
Person p = personBuilder
.name("Arthur Dent")
.age(42)
.build()
;
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
Person p = new Person("Arthur Dent", 42);
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.
Person p = new Person();
p.name("Arthur Dent");
p.age(42);
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 that age()
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()
and name()
before calling build()
. But you can't do it just by returning this
each time. Each thing that returns returns a different thing that forces you to call the next thing.
Use might look like this:
Person p = personBuilder
.name("Arthur Dent")
.age(42)
.build()
;
But this:
Person p = personBuilder
.age(42)
.build()
;
causes a compiler error because age()
is only valid to call on the type returned by name()
.
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.
chainable setters
:DwithName
return a copy of the Person with only the name field changed. In other words,Person john = new Person().withName("John");
could work even ifPerson
is immutable (and this is a common pattern in functional programming).void
methods. So, for example ifPerson
has a method that prints their name, you can still chain it with a Fluent Interfaceperson.setName("Alice").sayName().setName("Bob").sayName()
. By the way, I do annotate those in JavaDoc with exactly your suggestion@return Fluent interface
- it's generic and clear enough when it applies to any method that doesreturn this
at the end of its execution and it's fairly clear. So, a Builder will also be doing a fluent interface.