The Builder pattern is useful for creating complex objects that need step-by-step assembly. Or put more specifically, when a constructor just doesn’t do the trick anymore…
Builders are incredibly common and you are already using them. For instance:
- StringBuilder
- Fluent API
// Builders are very easy to spot "in the wild" because they look like this
Pizza pizza = new Pizza.Builder(12)
.cheese(true)
.pepperoni(true)
.bacon(true)
.build();
Builders “chain together” methods and allow you to append method one after another to create objects!
What builder pattern solves
Builders are an attempt to solve the problem of having too complicated constructors.
public DBConnection (String type, String password, bool ssl){ ... }
public DBConnection (String type, String password){ ...}
public DBConnection (String type, String password, int connections ){ ...}
Whenever you begin to notice your constructors becoming too large, a Builder pattern is the perfect way to solve the problem.
Builders also allow for our objects to maintain immutability.
Simple Builder Pattern
A simple builder pattern is comprised of 3 parts:
- Model/Object
- Builder
- Executor (basically, main() function or whatever is executing code)
public class DBConnection {
private String type;
private String password;
private bool isSsl;
private int connections;
public String getType() {
return type;
}
public String getPassword() {
return password;
}
public int getConnections() {
return connections;
}
public bool getIsSsl() {
return isSsl;
}
private DatabaseConnectionBuilder(Builder builder) {
this.type = builder.type;
this.password = builder.password;
this.isSsl = builder.isSsl;
this.connections = builder.connections;
}
}
Notice that there are no setters. This is because we want immutability!
Now that we have a model built, the next step is to make a builder.
public static class DBBuilder {
private String type;
private String password;
private bool isSSL;
private int connectionLimit;
public DBBuilder(String type, String password) {
this.type = type;
this.password = password;
}
public DBBuilder addSSL(bool isSSL) {
this.isSSL = isSSL;
return this;
}
public DBBuilder connectionLimit(int connectionLimit) {
this.connectionLimit = connectionLimit;
return this;
}
//Return the object
public DBConnection build() {
DBConnection dbConnection = new DBConnection(this);
return dbConnection;
}
}
Below is the code that we place inside our main:
public static void main(String[] args)
{
DBConnection _db = new DBConnection.DBBuilder("SQL", "secret");
.addSSL(true)
.connectionLimit(1)
.build();
}
Conclusion
The builder pattern does increase lines of code significantly, but we also gain flexibility and readability.
We no longer require multiple constructors and we can now instantiate objects with method chaining. Also, our objects don’t contain empty null
values which can cause bugs down the road.