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

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

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

Related

  • My ModelMapper Cheat Sheet
  • Builder Design Pattern in Java

Trending

  • Immutable Secrets Management: A Zero-Trust Approach to Sensitive Data in Containers
  • FIPS 140-3: The Security Standard That Protects Our Federal Data
  • Scaling DevOps With NGINX Caching: Reducing Latency and Backend Load
  • Beyond Simple Responses: Building Truly Conversational LLM Chatbots

A Dive into the Builder Pattern

By 
Nicolas Fränkel user avatar
Nicolas Fränkel
DZone Core CORE ·
Oct. 06, 13 · Interview
Likes (0)
Comment
Save
Tweet
Share
26.4K Views

Join the DZone community and get the full member experience.

Join For Free

The Builder pattern has been described in the Gang of Four “Design Patterns” book:

The builder pattern is a design pattern that allows for the step-by-step creation of complex objects using the correct sequence of actions. The construction is controlled by a director object that only needs to know the type of object it is to create.


A common implementation of using the Builder pattern is to have a fluent interface, with the following caller code:

Person person = new PersonBuilder().withFirstName("John").withLastName("Doe") .withTitle(Title.MR).build();
This code snippet can be enabled by the following builder:

public class PersonBuilder {
 
    private Person person = new Person();
 
    public PersonBuilder withFirstName(String firstName) {
 
        person.setFirstName(firstName);
 
        return this;
    }
 
    // Other methods along the same model
    // ...
 
    public Person build() {
 
        return person;
    }
}

The job of the Builder is achieved: the Person instance is well-encapsulated and only the build() method finally returns the built instance. This is usually where most articles stop, pretending to have covered the subject. Unfortunately, some cases may arise that need deeper work.

Let’s say we need some validation handling the final Person instance, e.g. the lastName attribute is to be mandatory. To provide this, we could easily check if the attribute is null in the build() method and throws an exception accordingly.

public Person build() {
 
    if (lastName == null) {
 
        throw new IllegalStateException("Last name cannot be null");
    }
 
    return person;
}
Sure, this resolves our problem. Unfortunately, this check happens at runtime, as developers calling our code will find (much to their chagrin). To go the way to true DSL, we have to update our design – a lot. We should enforce the following caller code:

Person person1 = new PersonBuilder().withFirstName("John").withLastName("Doe").withTitle(Title.MR).build(); // OK
 
Person person2 = new PersonBuilder().withFirstName("John").withTitle(Title.MR).build(); // Doesn't compile

We have to update our builder so that it may either return itself, or an invalid builder that lacks the build() method as in the following diagram. Note the first PersonBuilder class is kept as the entry-point for the calling code doesn’t have to cope with Valid-/InvaliPersonBuilder if it doesn’t want to.


This may translate into the following code:

public class PersonBuilder {
 
    private Person person = new Person();
 
    public InvalidPersonBuilder withFirstName(String firstName) {
 
        person.setFirstName(firstName);
 
        return new InvalidPersonBuilder(person);
    }
 
    public ValidPersonBuilder withLastName(String lastName) {
 
        person.setLastName(lastName);
 
        return new ValidPersonBuilder(person);
    }
 
    // Other methods, but NO build() methods
}
 
public class InvalidPersonBuilder {
 
    private Person person;
 
    public InvalidPersonBuilder(Person person) {
 
        this.person = person;
    }
 
    public InvalidPersonBuilder withFirstName(String firstName) {
 
        person.setFirstName(firstName);
 
        return this;
    }
 
    public ValidPersonBuilder withLastName(String lastName) {
 
        person.setLastName(lastName);
 
        return new ValidPersonBuilder(person);
    }
 
    // Other methods, but NO build() methods
}
 
public class ValidPersonBuilder {
 
    private Person person;
 
    public ValidPersonBuilder(Person person) {
 
        this.person = person;
    }
 
    public ValidPersonBuilder withFirstName(String firstName) {
 
        person.setFirstName(firstName);
 
        return this;
    }
 
    // Other methods
 
    // Look, ma! I can build
    public Person build() {
 
        return person;
    }
}

This is a huge improvement, as now developers can know at compile-time their built object is invalid.

The next step is to imagine more complex use-case:

  1. Builder methods have to be called in a certain order. For example, a house should have foundations, a frame and a roof. Building the frame requires having built foundations, as building the roof requires the frame.
  2. Even more complex, some steps are dependent on previous steps (e.g. having a flat roof is only possible with a concrete frame)

The exercise is left to interested readers. Links to proposed implementations welcome in comments.

There’s one flaw with our design: just calling the setLastName() method is enough to qualify our builder as valid, so passing null defeats our design purpose. Checking for null value at runtime wouldn’t be enough for our compile-time strategy. The Scala language features may leverage an enhancement to this design called the type-safe builder pattern.

Summary

  1. In real-life software, the builder pattern is not so easy to implement as quick examples found here and there
  2. Less is more: create an easy-to-use DSL is (very) hard
  3. Scala makes it easier for complex builder implementation’s designers than Java

Builder pattern

Published at DZone with permission of Nicolas Fränkel, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • My ModelMapper Cheat Sheet
  • Builder Design Pattern in Java

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!