Hello everyone, in this article I will start getting into the first design pattern, which is the Builder Pattern. Builder pattern is a pattern in the Creational Pattern. However, I will try to keep the examples as simple and accessible as possible, prioritizing practical implementations over confusing examples.
Problem
For this example, we will pretend that we are part of the Java team working on software for a bank. We will need a way to represent a bank account. This will be our class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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. } |
We can create it as simple as:
1 2 | BankAccount account = new BankAccount(123L, "Bart", 100.00); |
Unfortunately, the solutions are rarely that simple. A new request has appeared that says we track the monthly interest applied to each account, the date the account was opened and, optionally, the branch on which it was opened. So we give version 2.0 of the class BankAccount.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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 new and improved account processing, we have acquired a number of new clients.
1 2 3 | BankAccount account = new BankAccount(456L, "User", "Springfield", 100.00, 2.5); BankAccount anotherAccount = new BankAccount(789L, "Homer", null, 2.5, 100.00); //Oops! |
If we had 10 different parameters, it would be very difficult to determine what’s in the constructor at a glance. To make it worse, some of them might be optional, meaning we’ll need to create a bunch of overloaded constructors to deal with all possible combinations or them. We’ll have to pass null to our constructor (which sucks!).
You might think that we can mitigate the problem by calling a no-arg constructor and then setting up the account via setter methods. However, that leaves us open to another problem – what 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 won’t have any problems with it.
Therefore, there are two specific problems that we need to solve: 1. Too many constructor arguments. 2.The object state is incorrect. This is where the Builder pattern comes into play.
The Pattern
The builder pattern allows us to write easy-to-read, easy-to-understand code for constructing complex objects. It is usually deployed with a good-looking interface, which you may have seen in tools like Apache Camel or Hamcrest. Class builder will contain all the fields that exist on the class BankAccount itself. We’ll configure all the fields we want on the builder class and then we’ll use the builder class to create the account. At the same time, we will remove the public constructor constructor from the BankAccount class and replace it with a separate constructor so that accounts can only be created via the builder class.
It looks like this.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | 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. } |
Now we can create a new account as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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? It’s correct. Is it clearer? It’s correct. Is it better? Since a large portion of our time is spent reading code rather than writing it, I’m pretty sure it does.
summary
We worked through an example where the code started getting simple, and then became more complex. We then use the Builder pattern to solve the problems we discovered.
If you find yourself in a situation of continuing to add new parameters to a constructor, resulting in code becoming error-prone and difficult to read, then maybe it’s a good time to take a step back and consider refactoring the code. builder pattern to use.
Reference: https://dzone.com/articles/design-patterns-the-builder-pattern