I have seen many implementations of the Builder pattern (mainly in Java). All of them have an entity class (let's say a Person
class), and a builder class PersonBuilder
. The builder "stacks" a variety of fields and returns a new Person
with the arguments passed. Why do we explicitly need a builder class, instead of putting all the builder methods in the Person
class itself?
For example:
class Person {
private String name;
private Integer age;
public Person() {
}
Person withName(String name) {
this.name = name;
return this;
}
Person withAge(int age) {
this.age = age;
return this;
}
}
I can simply say Person john = new Person().withName("John");
Why the need for a PersonBuilder
class?
The only benefit I see, is we can declare the Person
fields as final
, thus ensuring immutability.
9 Answers 9
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.
-
3And then someone asks why you have to provide the name before the age, and the only answer is "because combinatorial explosion". I'm fairly sure one can avoid that in source if one has proper templates like C++, or goes back on using a different type for everything.Deduplicator– Deduplicator2018年10月22日 21:33:04 +00:00Commented Oct 22, 2018 at 21:33
-
2A leaky abstraction requires knowledge of an underlying complexity to be able to know how to use it. That's what I see here. Any class not knowing how to build itself is necessarily coupled to the Builder that has further dependencies (such is the purpose and nature of the Builder concept). One time (not at band camp) a simple need to instantiate an object required 3 months to undo just such a cluster frack. I kid you not.radarbob– radarbob2018年10月23日 15:01:41 +00:00Commented Oct 23, 2018 at 15:01
-
One of the advantages of classes that don't know any of their construction rules is that when those rules change they don't care. I can just add an
AnonymousPersonBuilder
that has it's own set of rules.candied_orange– candied_orange2018年10月23日 15:28:47 +00:00Commented Oct 23, 2018 at 15:28 -
Class/system design rarely shows deep application of "maximize cohesion and minimize coupling." Legions of design and runtime are extensible, and flaws fixable only if OO maxims and guidelines are applied with a sense of being fractal, or "it's turtles, all the way down."radarbob– radarbob2018年10月23日 15:42:02 +00:00Commented Oct 23, 2018 at 15:42
-
1@radarbob The class being built still knows how to build itself. Its constructor is responsible for ensuring the integrity of the object. The builder class is merely a helper for the constructor, because the constructor often takes a large number of parameters, which doesn't make for a very nice API.StackOverthrow– StackOverthrow2018年10月23日 21:33:55 +00:00Commented Oct 23, 2018 at 21:33
Why use/provide a builder class:
- To make immutable objects — the benefit you've identified already. Useful if the construction takes multiple steps. FWIW, immutability should be seen a significant tool in our quest to write maintainable and bug free programs.
- If the runtime representation of the final (possibly immutable) object is optimized for reading and/or space usage, but not for update. String and StringBuilder are good examples here. Repeatedly concatenating strings is not very efficient, so the StringBuilder uses a different internal representation that is good for appending — but not as good on space usage, and not as good for reading and using as the regular String class.
- To clearly separate constructed objects from objects under construction. This approach requires a clear transition from under-construction to constructed. For the consumer, there is no way to confuse an under-construction object with a constructed object: the type system will enforce this. That means sometimes we can use this approach to "fall into the pit of success", as it were, and, when making abstraction for others (or ourselves) to use (like an API or a layer), this can be a very good thing.
-
4Regarding the last bullet point. So the idea being that a
PersonBuilder
has no getters, and the only way to inspect the current values is to call.Build()
to return aPerson
. This way .Build can validate a properly constructed object, correct? I.e. this the mechanism intended to prevent use of an "under construction" object?AaronLS– AaronLS2018年10月22日 21:19:41 +00:00Commented Oct 22, 2018 at 21:19 -
2You could also validate your object within it's constructor, the preference may be personal or depend of the complexity. Whatever is chosen, the main point is that once you have your immutable object, you know it is properly built and has no unexpected value. An immutable object with an incorrect state shouldn't happen.Walfrat– Walfrat2018年10月23日 12:05:07 +00:00Commented Oct 23, 2018 at 12:05
-
3@AaronLS a builder can have getters; that’s not the point. It doesn’t need to, but if a builder has getters, you have to be aware that calling
getAge
on the builder may returnnull
when this property has not specified yet. In contrast, thePerson
class may enforce the invariant that age can never benull
, which is impossible when builder and actual object are mixed, like with the OP’snew Person() .withName("John")
example, which happily creates aPerson
without an age. This applies even to mutable classes, as setters may enforce invariants, but can’t enforce initial values.Holger– Holger2018年10月23日 13:21:46 +00:00Commented Oct 23, 2018 at 13:21 -
1@Holger your points are true, but mainly support the argument that a Builder should avoid getters.user949300– user9493002018年10月23日 21:24:37 +00:00Commented Oct 23, 2018 at 21:24
-
1@user949300 sure. I’m not advertising getters for builders. But the crucial point is that a builder may have an incomplete state while the object to build is supposed to always have a fully constructed state, right from the start. For an immutable object, that’s an unavoidable requirement.Holger– Holger2018年10月24日 10:02:47 +00:00Commented Oct 24, 2018 at 10:02
One reason would be to ensure that all of the passed-in data follows business rules.
Your example doesn't take this into consideration, but let's say that someone passed in an empty string, or a string consisting of special characters. You would want to do some sort of logic based around making sure that their name is actually a valid name (which is actually a very difficult task).
You could put that all in your Person class, especially if the logic is very small (for example, just making sure that an age is non-negative) but as the logic grows it makes sense to separate it.
-
7The example in the question provides a simple rule violation: there isn't an age given for
john
Caleth– Caleth2018年10月22日 15:26:45 +00:00Commented Oct 22, 2018 at 15:26 -
6+1 And it's very hard to do rules for two fields simultaneously without the builder class (fields X+Y can't be greater than 10).Reginald Blue– Reginald Blue2018年10月22日 20:15:17 +00:00Commented Oct 22, 2018 at 20:15
-
8@ReginaldBlue it's even harder if they're bound by "x+y is even". Our initial condition with (0,0) is OK, The state (1,1) is OK too, but there is no sequence of single variable updates that both keeps the state valid and gets us from (0,0) to (1,1).Michael Anderson– Michael Anderson2018年10月23日 07:25:44 +00:00Commented Oct 23, 2018 at 7:25
-
I believe this is the biggest advantage. All the others are nice-haves.Giacomo Alzetta– Giacomo Alzetta2018年10月24日 06:56:07 +00:00Commented Oct 24, 2018 at 6:56
A little different angle on this from what I see in other answers.
The withFoo
approach here is problematic because they behave like setters but are defined in a way that make it appear the class supports immutability. In Java classes, if a method modifies a property, it's customary to start the method with 'set'. I've never loved it as a standard but if you do something else it's going to surprise people and that's not good. There's another way that you can support immutability with the basic API you have here. For example:
class Person {
private final String name;
private final Integer age;
private Person(String name, String age) {
this.name = name;
this.age = age;
}
public Person() {
this.name = null;
this.age = null;
}
Person withName(String name) {
return new Person(name, this.age);
}
Person withAge(int age) {
return new Person(this.name, age);
}
}
It doesn't provide much in the way of preventing improperly pr partially constructed objects but does prevent changes to any existing objects. It's probably silly for this kind of thing (and so is the JB Builder). Yes you will create more objects but this is not as expensive as you might think.
You'll mostly see this kind of approach used with concurrent data structures such as CopyOnWriteArrayList. And this hints at why immutability is important. If you want to make your code threadsafe, immutability should almost always be considered. In Java, each thread is allowed to keep a local cache of variable state. In order for one thread to see the changes made in other threads, a synchronized block or other concurrency feature needs to be employed. Any of these will add some overhead to the code. But if your variables are final, there's nothing to do. The value will always be what it was initialized to and therefore all threads see the same thing no matter what.
Re-use builder object
As others have mentioned, immutability and verifying the business logic of all fields to validate the object are the major reasons for a separate builder object.
However re-usability is another benefit. If I want to instantiate many objects that are very similar I can make small changes to the builder object and continue instantiating. No need to recreate the builder object. This reuse allows the builder to act as a template for creating many immutable objects. It is a small benefit, but it could be a useful one.
Builder pattern is used to build/create the object step by step by setting the properties and when all the required fields are set then return the final object using build method. The newly created object is immutable. Main point to note here is that the object is only returned when the final build method is invoked. This insures that all the properties are set to the object and thus the object is not in inconsistent state when it is returned by the builder class.
If we dont use builder class and directly put all the builder class methods to the Person class itself, then we have to first create the Object and then invoke the setter methods on the created object which will lead to the inconsistent state of the object between creation of object and setting the properties.
Thus by using builder class (i.e some external entity other than Person class itself) we insure that the object will never be in the inconsistent state.
-
@DawidO you got my point. The main emphasis i am trying to put here is on the inconsistent state. And that is one of the main advantage of using Builder pattern.Shubham Bondarde– Shubham Bondarde2018年10月24日 05:28:09 +00:00Commented Oct 24, 2018 at 5:28
Actually, you can have the builder methods on your class itself, and still have immutability. It just means the builder methods will return new objects, instead of modifying the existing one.
This only works if there is a way of getting an initial (valid/useful) object (e.g. from a constructor which sets all the required fields, or a factory method which sets default values), and the additional builder methods then returns modified objects based on the existing one. Those builder methods need to make sure you don't get invalid/inconsistent objects on the way.
Of course, this means you'll have a lot of new objects, and you should not do this if your objects are expensive to create.
I used that in test code for creating Hamcrest matchers for one of my business objects. I don't recall the exact code, but it looked something like this (simplified):
public class CustomerMatcher extends TypeSafeMatcher<Customer> {
private final Matcher<? super String> nameMatcher;
private final Matcher<? super LocalDate> birthdayMatcher;
@Override
protected boolean matchesSafely(Customer c) {
return nameMatcher.matches(c.getName()) &&
birthdayMatcher.matches(c.getBirthday());
}
private CustomerMatcher(Matcher<? super String> nameMatcher,
Matcher<? super LocalDate> birthdayMatcher) {
this.nameMatcher = nameMatcher;
this.birthdayMatcher = birthdayMatcher;
}
// builder methods from here on
public static CustomerMatcher isCustomer() {
// I could return a static instance here instead
return new CustomerMatcher(Matchers.anything(), Matchers.anything());
}
public CustomerMatcher withBirthday(Matcher<? super LocalDate> birthdayMatcher) {
return new CustomerMatcher(this.nameMatcher, birthdayMatcher);
}
public CustomerMatcher withName(Matcher<? super String> nameMatcher) {
return new CustomerMatcher(nameMatcher, this.birthdayMatcher);
}
}
I would then use it like this in my unit tests (with suitable static imports):
assertThat(result, is(customer().withName(startsWith("Paŭlo"))));
-
1This is true--and I think it's a very important point, but it does not solve the last problem--It does not give you a chance to validate that the object is in a consistent state when it's constructed. Once constructed you will need some trickery like calling a validator before any of your business methods to make sure the caller set all the methods (which would be a terrible practice). I think it's good to reason through this kind of thing to see why we use the Builder pattern the way we do.Bill K– Bill K2018年10月23日 23:25:57 +00:00Commented Oct 23, 2018 at 23:25
-
1@BillK true - an object with, essentially, immutable setters (that return a new object every time) means that each object returned should be valid. If you are returning invalid objects (e.g., person with
name
but noage
), then the concept doesn't work. Well, you could actually be returning something likePartiallyBuiltPerson
while it's not valid but it seems like a hack to mask the Builder.VLAZ– VLAZ2018年10月24日 06:17:56 +00:00Commented Oct 24, 2018 at 6:17 -
1@BillK this is what I meant with "if there is a way of getting an initial (valid/useful) object" – I assume that both the initial object and the object returned from each builder function are valid/consistent. Your task as a creator of such a class is to provide only builder methods which do those consistent changes.Paŭlo Ebermann– Paŭlo Ebermann2018年10月24日 14:26:12 +00:00Commented Oct 24, 2018 at 14:26
Another reason that hasn't been explicitly mentioned here already, is that the build()
method can verify that all fields are 'fields contain valid values (either set directly, or derived from other values of other fields), which is probably the most likely failure mode that would otherwise occur.
Another benefit is that your Person
object would end up having a more simple lifetime and a simple set of invariant. You know that once you have a Person p
, you have a p.name
and a valid p.age
. None of your methods have to be designed to handle situations like "well what if age is set but not name, or what if name is set but not age?" This reduces the overall complexity of the class.
-
"[...] can verify that all fields are set [...]" The point of the Builder pattern is that you don't have to set all fields yourself and you can fall back to sensible defaults. If you need to set all fields anyway, maybe you should not use that pattern!Vincent Savard– Vincent Savard2018年10月22日 18:29:25 +00:00Commented Oct 22, 2018 at 18:29
-
@VincentSavard Indeed, but in my estimation, that's the most common usage. To validate that some "core" set of values are set, and do some fancy treatment around some optional ones (setting defaults, deriving their value from other values, etc.).Alexander– Alexander2018年10月22日 19:31:50 +00:00Commented Oct 22, 2018 at 19:31
-
Instead of saying 'verify that all fields are set', I would change this to 'verifying that all fields contain valid values [sometimes dependant on the values of other fields]' (ie. a field may only allow certain values depending on the value of another field). This could be done in each setter, but its easier for someone to set up the object without having exceptions thrown until the object is completed and ready to be built.yitzih– yitzih2018年10月22日 20:25:12 +00:00Commented Oct 22, 2018 at 20:25
-
The constructor of the class being built is ultimately responsible for the validity of its arguments. Validation in the builder is redundant at best, and could easily get out of sync with the requirements of the constructor.StackOverthrow– StackOverthrow2018年10月23日 21:47:25 +00:00Commented Oct 23, 2018 at 21:47
-
@TKK Indeed. But they validate different things. In many of the cases I've used Builder's in, the job of the builder was to construct the inputs that eventually get given to the constructor. E.g. the builder is configured with either a
URI
, aFile
, or aFileInputStream
, and uses whatever was provided to obtain aFileInputStream
that ultimately goes into the constructor call as an argAlexander– Alexander2018年10月23日 21:56:32 +00:00Commented Oct 23, 2018 at 21:56
A builder can also be defined to return an interface or an abstract class. You can use the builder to define the object, and the builder can determine what concrete subclass to return based on what properties are set, or what they are set to, for example.
Explore related questions
See similar questions with these tags.
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.