DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Deadlock-Free Synchronization in Java
  • Using the Chain of Responsibility Design Pattern in Java
  • Builder Design Pattern in Java
  • How Large Tech Companies Architect Resilient Systems for Millions of Users

Trending

  • A Developer's Guide to Mastering Agentic AI: From Theory to Practice
  • Top Book Picks for Site Reliability Engineers
  • Unlocking the Benefits of a Private API in AWS API Gateway
  • Google Cloud Document AI Basics

Design Patterns: The Builder Pattern

This look at the builder pattern takes complexity, verbosity, and clarity into consideration when implementing this classic Gang of Four design.

By 
Riaan Nel user avatar
Riaan Nel
·
Dec. 13, 16 · Tutorial
Likes (158)
Comment
Save
Tweet
Share
489.8K Views

Join the DZone community and get the full member experience.

Join For Free

I've been meaning to write a series of articles on design patterns for quite a while. Patterns are incredibly valuable components in a developer's toolbox – they address common problems that have accepted, effective solutions. In addition, they contribute to a shared vocabulary amongst developers.

This series assumes an understanding of object-oriented programming (OOP). I will, however, try to keep the examples as simple and accessible as possible, favoring practical implementations over obscure examples. If you're looking for an authoritative, academic text on patterns, this is what you want: Design Patterns: Elements of Reusable Object-Oriented Software.

We'll start with the Builder pattern (one of my favorites). The Builder pattern is a creational pattern – in other words, it's used to create and configure objects. I particularly like the example that Joshua Bloch uses in Effective Java.

The Problem

For this example, 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.

Summary

We worked through an example where code started out simple, and then grew in complexity. We then used the Builder pattern to address the issues that we uncovered.

If you find yourself in a situation where you keep on adding new parameters to a constructor, resulting in code that becomes error-prone and hard to read, perhaps it's a good time to take a step back and consider refactoring your code to use a Builder.

That's it for now. Feel free to drop a comment if you have anything to ask or to add.

Builder pattern Design code style

Published at DZone with permission of Riaan Nel. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Deadlock-Free Synchronization in Java
  • Using the Chain of Responsibility Design Pattern in Java
  • Builder Design Pattern in Java
  • How Large Tech Companies Architect Resilient Systems for Millions of Users

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!