Home > Development > A dive into the Builder pattern

A dive into the Builder pattern

The Builder pattern has been described in the Gang of Four “Design Patterns” book:

The builder pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. The construction is controlled by a director object that only needs to know the type of object it is to create.


A common implementation of using the Builder pattern is to have a fluent interface, with the following caller code:

Person person = new PersonBuilder().withFirstName("John").withLastName("Doe").withTitle(Title.MR).build();

This code snippet can be enabled by the following builder:

public class PersonBuilder {

    private Person person = new Person();

    public PersonBuilder withFirstName(String firstName) {

        person.setFirstName(firstName);

        return this;
    }

    // Other methods along the same model
    // ...

    public Person build() {

        return person;
    }
}

The job of the Builder is achieved: the Person instance is well-encapsulated and only the build() method finally returns the built instance. This is usually where most articles stop, pretending to have covered the subject. Unfortunately, some cases may arise that need deeper work.

Let’s say we need some validation handling the final Person instance, e.g. the lastName attribute is to be mandatory. To provide this, we could easily check if the attribute is null in the build() method and throws an exception accordingly.

public Person build() {

    if (lastName == null) {

        throw new IllegalStateException("Last name cannot be null");
    }

    return person;
}

Sure, this resolves our problem. Unfortunately, this check happens at runtime, as developers calling our code will find (much to their chagrin). To go the way to true DSL, we have to update our design – a lot. We should enforce the following caller code:

Person person1 = new PersonBuilder().withFirstName("John").withLastName("Doe").withTitle(Title.MR).build(); // OK

Person person2 = new PersonBuilder().withFirstName("John").withTitle(Title.MR).build(); // Doesn't compile

We have to update our builder so that it may either return itself, or an invalid builder that lacks the build() method as in the following diagram. Note the first PersonBuilder class is kept as the entry-point for the calling code doesn’t have to cope with Valid-/InvaliPersonBuilder if it doesn’t want to.


This may translate into the following code:

public class PersonBuilder {

    private Person person = new Person();

    public InvalidPersonBuilder withFirstName(String firstName) {

        person.setFirstName(firstName);

        return new InvalidPersonBuilder(person);
    }

    public ValidPersonBuilder withLastName(String lastName) {

        person.setLastName(lastName);

        return new ValidPersonBuilder(person);
    }

    // Other methods, but NO build() methods
}

public class InvalidPersonBuilder {

    private Person person;

    public InvalidPersonBuilder(Person person) {

        this.person = person;
    }

    public InvalidPersonBuilder withFirstName(String firstName) {

        person.setFirstName(firstName);

        return this;
    }

    public ValidPersonBuilder withLastName(String lastName) {

        person.setLastName(lastName);

        return new ValidPersonBuilder(person);
    }

    // Other methods, but NO build() methods
}

public class ValidPersonBuilder {

    private Person person;

    public ValidPersonBuilder(Person person) {

        this.person = person;
    }

    public ValidPersonBuilder withFirstName(String firstName) {

        person.setFirstName(firstName);

        return this;
    }

    // Other methods

    // Look, ma! I can build
    public Person build() {

        return person;
    }
}

This is a huge improvement, as now developers can know at compile-time their built object is invalid.

The next step is to imagine more complex use-case:

  1. Builder methods have to be called in a certain order. For example, a house should have foundations, a frame and a roof. Building the frame requires having built foundations, as building the roof requires the frame.
  2. Even more complex, some steps are dependent on previous steps (e.g. having a flat roof is only possible with a concrete frame)

The exercise is left to interested readers. Links to proposed implementations welcome in comments.

There’s one flaw with our design: just calling the setLastName() method is enough to qualify our builder as valid, so passing null defeats our design purpose. Checking for null value at runtime wouldn’t be enough for our compile-time strategy. The Scala language features may leverage an enhancement to this design called the type-safe builder pattern.

Summary

  1. In real-life software, the builder pattern is not so easy to implement as quick examples found here and there
  2. Less is more: create an easy-to-use DSL is (very) hard
  3. Scala makes it easier for complex builder implementation’s designers than Java
email
Send to Kindle
Categories: Development Tags: , ,
  1. Roger Parkinson
    October 6th, 2013 at 21:40 | #1

    Why wouldn’t you just use a factory for this eg
    PersonFactory.buildPerson(firstName,lastName,title)
    possibly use variable arg list. Then put your checks into the buildPerson method.
    You can make selecting the factory a bit more flexible with a factory-factory to allow the factory itself to vary (though I’ve yet to personally fund a use for that).
    Are there advantages the Builder addresses that the factory doesn’t?

  2. Nicolas Rémond
    October 7th, 2013 at 10:37 | #2

    If I ever do a Builder, like what you suggest, it’s because Person is *immutable*.
    In your case, I don’t see the purpose.

  3. October 7th, 2013 at 11:42 | #3

    @Nicolas Rémond
    To have valid objects, or do I miss your point?

  4. October 7th, 2013 at 11:45 | #4

    @Roger Parkinson

    1. - A varargs list is only possible for a single type, and completely disregard the args meaning (no more firstName, lastName, etc.)
    2. - Putting checks in the buildPerson() method is not compile-time safe
  5. Denis
    October 7th, 2013 at 17:06 | #5

    Nice design of the builder, specifically interesting the validation with invalid/valid builders. I think the builder is in general overlooked, is though a great pattern. I general to me is the most important thing is that the one who writes an api should make sure that any object that I create with his api is valid and I dont need to figure it out by myself. If you use a constructor, factory or a builder depends on your situation.

  6. Adam
    October 8th, 2013 at 01:14 | #6

    Yours is a really poorly designed and implemented pattern.
    First of all, most builders work with an enormous list of arguments (that’s why they are builders and not constructors). Moreover, in real life there are a lot of valid and invalid combinations and most of the logic cannot be checked compile time without creating extreme amount of useless “invalidbuilder” classes. Like “MissingFirstNameOrTitleButGotLastNameInvalidBuilder” or “GotConnectionStringMissingDriverOrJNDIInvalidBuilder” and so on.

  7. Stefan
    October 11th, 2013 at 21:18 | #7

    At my job the builder pattern is a hot discussed topic: A colleague and me we are proponents of the builder pattern. We like immutable value objects like Person classes, Address, EMail etc. that are created with a builder. We use runtime validation in the build() method to ensure correctness (compile time validation with a DSL would be great but is much more stuff to write and maintain….). The value class itself has final fields only that are set in a private constructor. If the value class has more than 5 members that is a sign of a missing concept and another value class. We create value classes until the constructor has 5 or less arguments again (e.g. extract an Address concept for a Person):

    public class Person {
    private final Name name;
    private final Address address;

    private Person(final Name name, final Address address) {
    this.name = name;
    this.address = address;
    }

    // getters only
    }
    public class PersonBuilder {
    private Person person;

    public PersonBuilder() {
    this.person = new Person(null, null);
    }
    public PersonBuilder withName(Name name) {
    this.person = new Person(name, person.address);
    return this;
    }
    public PersonBuilder withAddress(Address address) { … }

    public Person build() {
    validate();
    return person;
    }
    private void validate() {
    // validation logic here
    }
    }
    public class Name {
    private final String firstName;
    private final String lastName;

    …….
    }

    Pros:
    - Only valid Person objects leave the build() method into the wild. For example a Person instance must have a non null name and a non null address.
    - You can pass a builder around to express partial state or incomplete data but you need to call build to get a full and valid Person
    - No more NullPointerExceptions because every part of the application assumes that name and address are non null but there is one code path where Person instances are created without an address….
    - The no-more-than-5-constructor-arguments rule enforces the creation of suitable concepts and improves the design

    But there are two collegeas at my job who don’t share that opinion. They want to use dumb Java Bean like Objects with getter/setter pairs and default constructors. They say:
    - Builder pattern (as implemented above) is too complicated to understand
    - Builder pattern is too much code to write: We have to write twice the code for every member
    - Builder pattern is too inefficient: Too many objects created, too much stuff copied arround
    - When we want to create a Person who tells us that we need a PersonBuilder instead of calling a constructor?
    - Java Bean Pattern is simple, easy to understand and easy to use
    - no-more-than-five-constructor-arguments rule forces us to create too much objects and we have too read too much code to find the right porperty: If we want to get the firstname of a person, we don’t want to spend much time to find out that we have to call person.getName().getFirstName() — we want to call person.getFirstName() which is much much faster and easier to remember
    - if we have to create much more objects like Name and Address we have to implement builders for those too, which is a lot more code to write!

    ——————
    They don’t understand our arguments and I don’t know how to convince them to use immutable value objects and builders. Or am I wrong? Is this approach really too complex to use and understand?

  8. Rafik HAMID
    November 27th, 2013 at 22:03 | #8

    Nice article, I agree that most articles discussing Builder pattern do not handle data validation before the build nor method call ordering.

    That being said, I think this kind of builders is not productive as it takes too much effort and time to implement and maintain, in your example there is about 30 lines of code to build a class with 2 attributes, can you imagine how many lines of code it would take to create a Person in real life projects ?!!

    Besides, I see the person is not immutable, so the developer could easily overlook the builder and use the “new” to create persons. However, the code could be simplified by removing withFirstName() from InvalidPersonBuilder and withLastName() from ValidPersonBuilder, this way we only have the remaining attributes to provide.

  9. Max Kalininskij
    June 30th, 2014 at 05:32 | #9

    Make mandatory fields part of the Builder’s constructor, and optional fields as the standard instance methods returning an instance of the builder; setXXX

    So you’d have:

    NameBuilder nBuilder = new NameBuilder(firstName, lastName).setTitle(Title.MR).setMiddleName(middleName).build();

    What’s wrong with this approach exactly? It avoids all that huge boilerplate, inner classes, etc… that you’ve concocted up there.
    Don’t take my word for it though; this version of the pattern is right out of item #2 in Effective Java 2nd Edition, if memory serves me correct.

  10. July 4th, 2014 at 21:17 | #10

    @Max Kalininskij
    It works fine, until your builder needs to be called into a pre-defined sequence. Thanks for your comment anyway.

  1. No trackbacks yet.