37

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.

8
  • 5
    Note: The normal name for this pattern is chainable setters :D Commented Oct 22, 2018 at 20:04
  • 4
    Single responsibility principle. If you put the logic in the Person class then it has more than one job to do Commented Oct 22, 2018 at 22:05
  • 1
    Your benefit can be had either way: I can have withName return a copy of the Person with only the name field changed. In other words, Person john = new Person().withName("John"); could work even if Person is immutable (and this is a common pattern in functional programming). Commented Oct 23, 2018 at 5:30
  • 11
    @MooingDuck: another common term for it is a fluent interface.
    – Mac
    Commented Oct 23, 2018 at 6:43
  • 3
    @Mac I think the fluent interface is a bit more general - you avoid having void methods. So, for example if Person has a method that prints their name, you can still chain it with a Fluent Interface person.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 does return this at the end of its execution and it's fairly clear. So, a Builder will also be doing a fluent interface.
    – VLAZ
    Commented Oct 23, 2018 at 6:56

9 Answers 9

34

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.

9
  • 3
    And 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. Commented Oct 22, 2018 at 21:33
  • 2
    A 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
    Commented 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. Commented 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
    Commented 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. Commented Oct 23, 2018 at 21:33
59

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.
6
  • 4
    Regarding 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 a Person. 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
    Commented Oct 22, 2018 at 21:19
  • 2
    You 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
    Commented 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 return null when this property has not specified yet. In contrast, the Person class may enforce the invariant that age can never be null, which is impossible when builder and actual object are mixed, like with the OP’s new Person() .withName("John") example, which happily creates a Person without an age. This applies even to mutable classes, as setters may enforce invariants, but can’t enforce initial values.
    – Holger
    Commented Oct 23, 2018 at 13:21
  • 1
    @Holger your points are true, but mainly support the argument that a Builder should avoid getters.
    – user949300
    Commented 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
    Commented Oct 24, 2018 at 10:02
21

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.

4
  • 7
    The example in the question provides a simple rule violation: there isn't an age given for john
    – Caleth
    Commented 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). Commented 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). Commented Oct 23, 2018 at 7:25
  • I believe this is the biggest advantage. All the others are nice-haves. Commented Oct 24, 2018 at 6:56
5

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.

0
4

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.

2

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.

1
  • @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. Commented Oct 24, 2018 at 5:28
2

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"))));
3
  • 1
    This 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
    Commented 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 no age), then the concept doesn't work. Well, you could actually be returning something like PartiallyBuiltPerson while it's not valid but it seems like a hack to mask the Builder.
    – VLAZ
    Commented 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. Commented Oct 24, 2018 at 14:26
1

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.

5
  • "[...] 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! Commented 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
    Commented 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
    Commented 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. Commented 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, a File, or a FileInputStream, and uses whatever was provided to obtain a FileInputStream that ultimately goes into the constructor call as an arg
    – Alexander
    Commented Oct 23, 2018 at 21:56
1

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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