Skip to main content

Command Palette

Search for a command to run...

Design Patterns #4: Builder Pattern

Solving Constructor Hell

Updated
4 min read
Design Patterns #4: Builder Pattern
A

Fullstack Developer | CSS | JavaScript | React | Angular | Web3

Before learning Builder Pattern, let's first understand the problem it tries to solve.

Suppose we have a User class.

class User {

    private String name;
    private String email;
    private String phone;
    private String address;
    private int age;
    private boolean isPremium;

    User(
        String name,
        String email,
        String phone,
        String address,
        int age,
        boolean isPremium
    ) {
        this.name = name;
        this.email = email;
        this.phone = phone;
        this.address = address;
        this.age = age;
        this.isPremium = isPremium;
    }
}

Creating a user:

User user = new User(
    "Abhinandan",
    "abhinandan@gmail.com",
    "9876543210",
    "Bangalore",
    25,
    true
);

Initially, it looks okay, but while using it we have to remember which property is being passed at which position.

What Happens As Requirements Grow?

Tomorrow the business asks for:

country
company
linkedinProfile
githubProfile
preferredLanguage
profilePicture

Now:

User user = new User(
    "Abhinandan",
    "abhinandan@gmail.com",
    "9876543210",
    "Bangalore",
    25,
    true,
    "India",
    "Project44",
    "...",
    "...",
    "English",
    "..."
);

Now, I don't even know which parameter is the 7th and which is the 10th.

The constructor becomes difficult to read and maintain.

This is often called:

Constructor Hell

A Common Solution

One possible solution is to create multiple constructors.

User(String name)

User(String name, String email)

User(
    String name,
    String email,
    String phone
)

But as the number of fields increases, the number of constructors also keeps increasing.

Soon we end up with constructor explosion, which again becomes difficult to maintain.

Builder Pattern Solution

Instead of passing everything through a constructor, we can create the object step by step.

Let's create a Builder class inside the User class.

class User {

    private String name;
    private String email;
    private String phone;
    private String address;
    private int age;
    private boolean isPremium;

    private User(Builder builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.phone = builder.phone;
        this.address = builder.address;
        this.age = builder.age;
        this.isPremium = builder.isPremium;
    }

    public static Builder builder() {
        return new Builder();
    }

    static class Builder {

        private String name;
        private String email;
        private String phone;
        private String address;
        private int age;
        private boolean isPremium;

        Builder name(String name) {
            this.name = name;
            return this;
        }

        Builder email(String email) {
            this.email = email;
            return this;
        }

        Builder phone(String phone) {
            this.phone = phone;
            return this;
        }

        Builder address(String address) {
            this.address = address;
            return this;
        }

        Builder age(int age) {
            this.age = age;
            return this;
        }

        Builder premium(boolean isPremium) {
            this.isPremium = isPremium;
            return this;
        }

        User build() {
            return new User(this);
        }
    }
}

Now creating a user becomes much cleaner.

User user = User.builder()
    .name("Abhinandan")
    .email("abhinandan@gmail.com")
    .phone("9876543210")
    .age(25)
    .premium(true)
    .build();

Now we immediately know:

  • Which property is being set

  • Which properties are optional

  • What value belongs to which field

The code is much more readable.

Why Builder Pattern?

Better Readability

Instead of:

new User(
    "Abhinandan",
    "abhinandan@gmail.com",
    "9876543210",
    ...
);

we get:

User.builder()
    .name("Abhinandan")
    .email("abhinandan@gmail.com")
    .build();

which is much easier to understand.

Optional Fields

Suppose phone and address are optional.

Without Builder:

new User(
    "Abhinandan",
    "abhinandan@gmail.com",
    null,
    null,
    25,
    false
);

With Builder:

User.builder()
    .name("Abhinandan")
    .email("abhinandan@gmail.com")
    .build();

No unnecessary null values.

Validation Before Object Creation

Builder can also validate data before creating the object.

User build() {

    if(name == null) {
        throw new RuntimeException(
            "Name is required"
        );
    }

    return new User(this);
}

This ensures invalid objects are not created.

Now this is all about the Builder pattern and you will understand better while actually desigining systems. There are a lot of other patterns but now we're going to solve some problems then move ahead with learning more patterns.