Java

Java Builder Pattern

The Builder pattern is a creational pattern – in other words, it’s used to create and configure objects.

We’ll pretend that we’re part of a Java team working on a piece of software for a bank. Among other things, we’ll need a way to represent bank accounts. Our first pass looks like this (note that using double for actual monetary values is a bad idea).

public class BankAccount {
    private long accountNumber;
    private String owner;
    private double balance;
    public BankAccount(long accountNumber, String owner, double balance) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = balance;
    }
    //Getters and setters omitted for brevity.
}

This is reasonably straightforward — we can use it as follows.

BankAccount account = new BankAccount(123L, "Bart", 100.00);

Unfortunately, solutions rarely stay simple. A new requirement arrives that says that we should keep track of the monthly interest rate applicable to each account, the date on which it was opened, and, optionally, the branch at which it was opened. It sounds easy enough, so we come up with version 2.0 of the BankAccount class.

public class BankAccount {
    private long accountNumber;
    private String owner;
    private String branch;
    private double balance;
    private double interestRate;
    public BankAccount(long accountNumber, String owner, String branch, double balance, double interestRate) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.branch = branch;
        this.balance = balance;
        this.interestRate = interestRate;
   }
    //Getters and setters omitted for brevity.
}

Thanks to our new and improved account handling process, we get some new clients.

BankAccount account = new BankAccount(456L, "Marge", "Springfield", 100.00, 2.5);
BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00);  //Oops!

Our compiler, which should be our safety net, thinks that this code is fine. The practical implication, however, is that Homer’s money will double every month. (If anyone knows of an account with returns like this, please let me know!) Can you figure out why? Hint: pay close attention to the order of the parameters passed to the constructor.

If we have multiple consecutive arguments of the same type, it’s easy to accidentally swap them around. Since the compiler doesn’t pick it up as an error, it can manifest as an issue somewhere down the line at runtime – and that can turn into a tricky debugging exercise. In addition, adding more constructor parameters results in code that becomes harder to read . If we had 10 different parameters, it would become very difficult to identify what’s what in the constructor at a single glance. To make it worse, some of those values might be optional, which means that we’ll need to create a bunch of overloaded constructors to deal with all possible combinations, or we’ll have to pass nulls to our constructor (ugly!).

You might be thinking that we can mitigate the issue by calling a no-arg constructor and then setting up the account via setter methods instead. However, that leaves us open to another issue – what happens if a developer forgets to call a particular setter method? We could end up with an object that is only partially initialized, and again, the compiler wouldn’t see any problems with it.

Thus, there are two specific problems that we need to solve:

  • Too many constructor arguments.
  • Incorrect object state.

This is where the Builder pattern comes into play.

The Pattern

The Builder pattern allows us to write readable, understandable code to set up complex objects. It is often implemented with a fluent interface, which you may have seen in tools like Apache Camel or Hamcrest. The builder will contain all of the fields that exist on the BankAccount class itself. We will configure all of the fields that we want on the builder, and then we’ll use the builder to create accounts. At the same time, we’ll remove the public constructor from the BankAccount class and replace it with a private constructor so that accounts can only be created via the builder.

For our example, we’ll put the builder within the BankAccount class. It looks like this.

public class BankAccount {
    public static class Builder {
        private long accountNumber; //This is important, so we'll pass it to the constructor.
        private String owner;
        private String branch;
        private double balance;
        private double interestRate;
        public Builder(long accountNumber) {
            this.accountNumber = accountNumber;
        }
        public Builder withOwner(String owner){
            this.owner = owner;
            return this;  //By returning the builder each time, we can create a fluent interface.
        }
        public Builder atBranch(String branch){
            this.branch = branch;
            return this;
        }
        public Builder openingBalance(double balance){
            this.balance = balance;
            return this;
        }
        public Builder atRate(double interestRate){
            this.interestRate = interestRate;
            return this;
        }
        public BankAccount build(){
            //Here we create the actual bank account object, which is always in a fully initialised state when it's returned.
            BankAccount account = new BankAccount();  //Since the builder is in the BankAccount class, we can invoke its private constructor.
            account.accountNumber = this.accountNumber;
            account.owner = this.owner;
            account.branch = this.branch;
            account.balance = this.balance;
            account.interestRate = this.interestRate;
            return account;
        }
    }
    //Fields omitted for brevity.
    private BankAccount() {
        //Constructor is now private.
    }
    //Getters and setters omitted for brevity.
}

We can now create new accounts as follows.

BankAccount account = new BankAccount.Builder(1234L)
            .withOwner("Marge")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();
BankAccount anotherAccount = new BankAccount.Builder(4567L)
            .withOwner("Homer")
            .atBranch("Springfield")
            .openingBalance(100)
            .atRate(2.5)
            .build();

Is this code more verbose? Yes. Is it clearer? Yes. Is it better? Since a large chunk of our time is spent reading code rather than writing it, I’m pretty sure it is, yes.

Leave a comment