Simplifying Object Creation With The Builder Pattern

Reed Odeneal
Nov 22, 2020 · 4 min read
Image for post
Image for post
If you Builder it…they will read your code easier.

Design patterns are reusable and well-accepted strategies for solving common problems in software architecture. In this post, I’m going to talk about the Builder pattern. One of the Gang of Four design patterns, the Builder pattern can be used to encapsulate and control the creation of a complex object by implementing a separate, concrete Builder class to delegating object creation to rather than trying to build directly through a convoluted constructor. Not only will following this pattern make it easier to instantiate your complex class but it can produce code that is much easier to read and follow — potentially preventing bugs.

I’ll be showing an example of a problem that I ran into recently where I had to create instances of a rather complex object due to some requirements that called for additional, optional constructor parameters.

Example

public class Event {
private final String type;
private final String sender;
private final Map<String, Object> payload;

private Date created;
private boolean retryable;
private int maxRetries;
private int attemptCount;
private Date lastUpdated;

public Event(String type, String sender, Map<String, Object> payload) {
this.type = type;
this.sender = sender;
this.payload = payload;
this.created = new Date();
}

// additional methods omitted for example

}

In this Event class, we have three final fields that we are required to set in our constructor. Our optional fields are here to help us control the retryability of an Event if needed.

We could add them to a new constructor, like so:

public Event(String type, String sender, Map<String, Object> payload, boolean retryable,
int maxRetries, int attemptCount, Date lastUpdated) {
this.type = type;
this.sender = sender;
this.payload = payload;
this.created = new Date();
this.retryable = retryable;
this.maxRetries = maxRetries;
this.attemptCount = attemptCount;
this.lastUpdated = lastUpdated;
}

This starts to add complexity and makes our code less readable and could get messy if we need more control mechanisms to pass to an Event. Another option is to add some setter methods to set the optional retry fields on our Event class.

public void setRetryable(boolean retryable) {
this.retryable = retryable;
}

public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}

public void setAttemptCount(int attemptCount) {
this.attemptCount = attemptCount;
}

public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}

With the setter method approach, if we add even more additional fields in the future we need to remember to call every setter method for our optional fields. Forgetting one could create a runtime exception without some kind of null check in our implementation. Additionally, this could leave an Event in a partial state in between the constructor and setter method class which could have further implications in multithreading situations. So, what do?

Enter, the Builder class. Here, we create a nested, public Builder class inside of our Event class. We create it inside of our Event class so we don’t have to stray far from our code which, in the future, will allow you or another developer to more easily remember to update the Builder when changing the interface of the Event object.

public class Event {

private final String type;
private final String sender;
private final Map<String, Object> payload;

private Date created;
private boolean retryable;
private int maxRetries;
private int attemptCount;
private Date lastUpdated;

private Event(String type, String sender, Map<String, Object> payload) {
this.type = type;
this.sender = sender;
this.payload = payload;
this.created = new Date();
}

public Event(String type, String sender, Map<String, Object> payload, boolean retryable,
int maxRetries, int attemptCount, Date lastUpdated) {
this.type = type;
this.sender = sender;
this.payload = payload;
this.created = new Date();
this.retryable = retryable;
this.maxRetries = maxRetries;
this.attemptCount = attemptCount;
this.lastUpdated = lastUpdated;
}

public void setRetryable(boolean retryable) {
this.retryable = retryable;
}

public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}

public void setAttemptCount(int attemptCount) {
this.attemptCount = attemptCount;
}

public void setLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
}

public static class Builder {

private final String type;
private final String sender;
private final Map<String, Object> payload;

private boolean retryable = false;
private int maxRetries = 0;
private int attemptCount = 0;
private Date lastUpdated = new Date();

public Builder(String type, String sender, Map<String, Object> payload) {
this.type = type;
this.sender = sender;
this.payload = payload;
}

public Builder isRetryable(boolean retryable) {
this.retryable = retryable;
return this;
}

public Builder withMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}

public Builder withAttemptCount(int attemptCount) {
this.attemptCount = attemptCount;
return this;
}

public Builder withLastUpdated(Date lastUpdated) {
this.lastUpdated = lastUpdated;
return this;
}

public Event build() {
Event event = new Event(type, sender, payload);
event.setRetryable(retryable);
event.setAttemptCount(attemptCount);
event.setMaxRetries(maxRetries);
event.setAttemptCount(attemptCount);
event.setLastUpdated(lastUpdated);
return event;
}
}

}

The Builder class we have created encapsulates, assembles, and creates our Event object now and is called like this:

Map<String, Object> payload = new HashMap<>();
payload.put("message", "Hi!");
Event event = new Event.Builder("myType", "Reed", payload)
.isRetryable(true)
.withMaxRetries(2)
.build();

This is a cleaner approach rather than slugging through a long constructor creation.

Delegating Control

private Event(String type, String sender, Map<String, Object> payload) {
this.type = type;
this.sender = sender;
this.payload = payload;
this.created = new Date();
}
// disables the ability to call new Event() outside this class

Conclusion

Links:

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data…

Sign up for Analytics Vidhya News Bytes

By Analytics Vidhya

Latest news from Analytics Vidhya on our Hackathons and some of our best articles! Take a look.

By signing up, you will create a Medium account if you don’t already have one. Review our Privacy Policy for more information about our privacy practices.

Check your inbox
Medium sent you an email at to complete your subscription.

Reed Odeneal

Written by

Senior Software Engineer at SageSure

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Reed Odeneal

Written by

Senior Software Engineer at SageSure

Analytics Vidhya

Analytics Vidhya is a community of Analytics and Data Science professionals. We are building the next-gen data science ecosystem https://www.analyticsvidhya.com

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store