The Builder Pattern

The Builder Pattern

The builder software design pattern is an object creation one. It’s a great building block for classes with optional fields. It provides a maintainable and readable way to instantiate immutable classes with more than 3 or 4 fields. It greatly reduces the time developers needs to understand how to instantiate the class properly.

You have surely faced one day or another a code similar to the one below.

public class Person {
  private final String firstname;
  private final String lastname;
  private final String address;
  private final String zipCode;

  public Person(final String firstname, final String lastname) {
    this(firstname, lastname, "");
  }

  public Person(final String firstname, final String lastname, final String address) {
    this(firstname, lastname, address, "");
  }

  public Person(final String firstname, final String lastname, final String address, final String zipCode) {
    this.firstname = Preconditions.nonNull(firstname);
    this.lastname = Preconditions.nonNull(lastname);
    this.address = Preconditions.nonNull(address);
    this.zipCode = Preconditions.nonNull(zipCode);
  }
}

Telescoping constructors in Person class

It can also happen in static factories:

public final class PersonFactory {

  private PersonFactory() {
    super();
  }

  public static Person create(final String firstname, final String lastname) {
    return new Person(firstname, lastname, "", "");
  }

  public static Person create(final String firstname, final String lastname, final String address) {
    return new Person(firstname, lastname, address, "");
  }
}

In the example above, address and zipCode are apparently optional. It means that a Person can be instantiated with only a firstname and a lastname. Telescoping constructors is an anti-pattern: it’s a bad practice which leads to poor developer productivity.

Telescoping constructor is widely used when creating objects with optional fields. The fact is telescoping constructors or static factories is hard to read and hard to maintain. The developers have to go through a number of constructors with a variety of fields to find the one which suits their needs.

We’re going to explore how the builder pattern can solve the constructor / static factory telescoping.

The construction issue

Also, when instantiating an object with many fields, it usually looks like:

final Person person = new Person("John", "Smith", "1 infinite loop road", 12345);

In this case, the fields are pretty obvious. But, with more complex classes it can be quickly unreadable. Developers start to feel the need to go through the class’s code to check what the constructor does. This is a waste of time. The less developers need to care about the classes they are using the more they can focus on business code.

Split or Build

As soon as a class has more than 3 or 4 fields, you should first ask yourself if you should split the class into smaller ones. In the following example, it’s better to split the class into a smaller one and leave all fields mandatory:

public final class Person {
  private final String firstname;
  private final String lastname;
  private final Address address;

  public Person(final String firstname, final String lastname, final Address address) {
    this.firsname =  Preconditions.nonNull(firstname);
    this.lastname = Preconditions.nonNull(lastname);
    this.address = Preconditions.nonNull(address);
  }
}

public final class Address {
  private final String details;
  private final int zipCode;

  public Address(final String details, final int zipCode) {
    this.details = details;
    this.zipCode = zipCode;
  }
}

As a general rule, it’s not recommended to create builders for objects with an insane (let’s say over 10 fields) number of internal fields, but split them into smaller objects instead. One could argue that the best way to avoid having to initialize all the class fields is to introduce mutability. Mutable objects, in contradiction to Immutable Objects, don’t need to be fully initialized to be instantiated.

Here is an example of a mutable Person:

public class Person {
  private String firstname = "";
  private String lastname = "";
  private Address address;

  /* Getters and setters here */
}

As explained in our blog post Why objects should be immutable, we recommend to avoid mutable objects to solve software design issues. Mutability is the root of all evil. By making the Person class mutable, sure you don’t need to initialize all fields anymore. But think of what may happen if someone wildly sets a field, or forgets to initialize a field. Mutability is the wrong answer to constructor telescoping.

A mutable Java Bean may be in an inconsistent state while immutables objects can’t. This huge difference weights a lot in productivity. If you have ever struggled to find why a mutable object hasn’t been properly initialized when your code faces it, you understand why immutability is important.

By using a builder, we move the mutability to another class while leaving the Person class immutable:

public class Person {
  ...

  public static PersonBuilder builder() {
    return new PersonBuilder();
  }	
}

public class PersonBuilder {
  private String firstname = "";
  private String lastname = "";
  private Address address = new Address("",0);

  public PersonBuilder firstname(final String firstname) {
    this.firstname = Preconditions.nonNull(firstname);
    return this;
  }

  /* And so on */

  public Person build() {
    return new Person(firstname, lastname, address);
  }
}

Instantiating a Person becomes really easy to write and easy to read:

final Person person = Person.builder().firstname("John").lastname("Smith").build();

Constructing an instance via a builder is like having a constructor with named fields. It greatly reduces the number of construction mistakes compared to a constructor with multiple parameters.

Preconditions

Like constructors, builders can enforce preconditions when constructing an instance:

public class PersonBuilder {
  ...

  public PersonBuilder firstname(final String firstname) {
    if(firstname == null) {
      throw new IllegalArgumentException("firstname is NULL");
    }
    this.firstname = Strings.nonEmpty(firstname);
    return this;
  }
}

It’s perfect to enforce proper instantiation. When the developer encounters an instance of the Person class, he knows the firstname is properly set and valid.

Invariants

Like Preconditions, builders can keep class invariants. Invariants are constraints that are always true before and after an operation on an instance of the class. For example, a list which keeps a size field has the following invariant: the size reflects the number of elements inside the list.

Disadvantages

While builders have a number of advantages, there are also some small downsides you need to be aware of.

First, instantiating an instance requires to instantiate the builder first. While object allocation is cheap, it could be noticeable in applications where performance is critical. The golden rule is: don’t optimize unless it’s necessary to. In other words, don’t argue you avoid builders because of performances unless you are told your code is too slow.

Second, builders encourage building large and complex objects. As instantiating objects with many fields because easier and more readable, you tend to write bigger objects. It’s up to the developer to know when a class should be broken down into smaller pieces. Usually, objects with over 10 fields are becoming too complex and should be refactored. Altough, I must admit we have several objects with up to 20 fields in OctoPerf. I guess, sometimes exceptions can be made when it doesn’t hurt the overall software design.

Last, but not least, builders are not thread-safe. While immutable objects can be freely shared between threads, builders cannot because of the mutable state. But, who really needs to instantiate a class in a multithreaded manner?

Lombok

Writing builders is a painful task with a high risk of failure due to its repetitive and boring nature. Project Lombok is a Java library which makes it insanely safe and fast to write builders. Simply annotate your class with @Builder and you’re good to go:

@Value
@Builder
public class Person {
  String firstname;
  String lastname;
}

Combined with the @Value annotation, the code above is equivalent to an immutable Person class with a PersonBuilder, with generated hashcode and equals methods. I strongly recommend using Lombok in all your projects since it can save a huge amount of code to write.

Conclusion

Builders are great to instantiate classes with many optional fields. As the code is easier to write and easier to maintain, the builder is great to improve development productivity and reduce brain overload. The purpose of advanced development techniques is not to write beautiful code: it’s all about outpacing your competitors.

By - CTO.
Tags: Java Best Practices Lombok

Comments

 

Thank you

Your comment has been submitted and will be published once it has been approved.

OK

OOPS!

Your post has failed. Please return to the page and try again. Thank You!

OK