The Builder Pattern

Builders make large data objects simpler to create, read, and maintain, and they are a great way to maintain immutable data objects and give default values.

This is probably the most-important pattern in Java I’ve used in my career. It would have saved me tens of hours while doing homework back in university, and it’s a pattern every Java developer should know.

Have you ever come across a class with a zillion constructor parameters? Did you have to add or remove a field? Those who have are experiencing war-like flashbacks right now filled with nothing but horror and dread.

It’s a pain to extend or shorten constructors, especially in large code bases. Updating the tests is even worse. Eventually, a mistake is made and values are accidentally mis-ordered. All of a sudden, a user’s location is being used as their username!

This is what code reviews are for, but humans still make mistakes. Even worse, maybe an old test was insufficient, and come release time, usernames and passwords no longer work! The values have been switched or aren’t being set and now no user can log in!

Time for a rollback! …Joy.

Don’t make classes with numerous incoming values. Don’t be that person.

Java has a way to alleviate this pain: the Builder pattern. It should be taught in every Java 201 class, but since that class doesn’t really exist at most universities, let’s look at it here.

Here is an example of the ugliness we’re hoping to avoid:

public class LargeDataClass {

    private final int value1;
    private final int value2;
    ...
    private final int value42;

    public LargeDataClass(int value1, int value2, ..., int value42) {
        this.value1 = value1;
        this.value2 = value2;
        ...
        this.value42 = value42;
    }

While coming across an object that has upwards of 42 internal values is unusual, they’re not unheard of. To add to its complications, there is no way to specify default values for parameters unless multiple constructors are defined. But if many of the values an object is expecting share the same type, constructor definitions may clash, meaning only some values can have defaults set.

A Builder is verbose to create, but it saves countless developer and debugging hours in the future. It’s cleaner and easier to read as it help to describe objects developers create as they’re being made. It’s much easier to maintain as it’s easier to alter and update throughout time. Plus, it gives the developer a way to specify default values and check for parameter mistakes.

Here is the same data object built using the Builder pattern:

public class LargeDataClass {

    private final int value1;
    private final int value2;
    ...
    private final int value42;

    private LargeDataClass(LargeDataClassBuilder largeDataClassBuilder) {
        this.value1 = largeDataClassBuilder.value1;
        this.value2 = largeDataClassBuilder.value2;
        ...
        this.value42 = largeDataClassBuilder.value42;
    }

    public static class LargeDataClassBuilder{

        // Required variables
        private final int value1;
        private final int value2;

        // Optional variables
        private int value3 = 0;
        private int value4 = 121;
        private int value42 = 5;

        private LargeDataClassBuilder(int value1, int value2) {
            this.value1 = value1;
            this.value2 = value2;
        }

        public LargeDataClassBuilder withValue3(int value3) {
            this.value3 = value3;
            return this;
        }

        ...
        public LargeDataClassBuilder withValue42(int value42) {
            this.value42 = value42;
            return this;
        }

        public LargeDataClass build(){
            if(value1 < 0 || value2 > 100 || value4 % 2 == 1){
                throw new RuntimeException("Invalid values given for LargeDataClassBuilder!");
            }

            return new LargeDataClass(this);
        }
    }
}

Lots of changes. So what is going on here?

The constructor of the object is now private to force developers to create the object using the corresponding builder class and its setter methods. Those are included as a static class inside the object.

Each field can now has a corresponding setter method and can be set as needed. For optional fields, default values can be included where applicable. Instances in the code base only need to be updated if they’re setting fields recently added to the object, and fields being removed will result in a compilation error which gives the developer an exact line to remove. If the data object expands or shrinks, the rest of the code base only needs to change if it needs to handle the change.

What’s more is each value can be set and read in near-english. With the original class declaration, a developer may ask “what does the 13th element in the constructor represent?” That can take some time to figure out. Builders give great readability, e.g., “withUsername(someUsernameValue);”. This is especially nice as a person who isn’t familiar with the code base or has to come back to a project at a much later date.

To finalize the creation of the data object, a developer must call the build method. Here, if desired, value and type checks can be conducted for safety.

Requiring values in the Builder’s constructor is optional especially if every field in the resulting object is required – something which can be checked in the build method. Just be aware that this pushes the check for these values from compile-time to run-time.

In general, I’d say if there are more than four constructor parameters needed to create an object, this pattern should be used.

Creating the object is much nicer now. We’ve gone from this ugliness:

LargeDataClass ldc = new LargeDataClass(1, 43, 21, ...,43);

To something that’s much easier to read and understand, and something that pinpoints the exact changes come code-review time:

LargeDataClass ldc = new LargeDataClassBuilder(1, 2)
      .withValue3(3)
      .withValue4(23)
      ...
      .withValue42(42)
      .build();

This pattern is rather unique to strictly-typed languages. Other languages, like Python, have named parameters along with default parameters, which makes using a pattern like this redundant. The Builder pattern is a way to give parameters names and keep them organized in Java while improving readability and maintainability.

Advertisements

One thought on “The Builder Pattern

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s