Creating objects in a fancy way: Builder Pattern

One of the most used Design Patterns is the Builder Pattern. As Joshua Bloch stated in his excellent book Effective Java, the use of that pattern results in a fluent API with the code easy to write and, more importantly, easy to read since it simulates named optional parameters as found in Python and Scala.

In this article, we’ll discuss the problem it solves and how to implement it.

We’ll start by creating a Quarkus project:

mvn io.quarkus:quarkus-maven-plugin:1.5.2.Final:create 
    -DprojectGroupId=br.com.helberbelmiro 
    -DprojectArtifactId=builder-pattern 
    -DclassName="br.com.helberbelmiro.builderpattern.PeopleApi" 
    -Dpath="/people" 
    -Dextensions="resteasy-jsonb"
    cd builder-pattern

Let’s say we have this Person class with non-null attributes:

public class Person {
    private final String name;

    private final LocalDate birthDate;

    private final String email;

    private final String phoneNumber;

    private final String website;

    private final String facebookAccount;

    private final String instagramAccount;

    private final String twitterAccount;

    private final String githubAccount;   
    // Getters
}

How would you define the constructor of that class?

Since we have 9 optional attributes to initialize, there aren’t many great alternatives that we could use. I would create a single constructor that receives the initial values for all attributes:

public Person(String name, 
              LocalDate birthDate, 
              String email, 
              String phoneNumber, 
              String website, 
              String facebookAccount, 
              String instagramAccount, 
              String twitterAccount, 
              String githubAccount) {
    this.name = name;
    this.birthDate = birthDate;
    this.email = email;
    this.phoneNumber = phoneNumber;
    this.website = website;
    this.facebookAccount = facebookAccount;
    this.instagramAccount = instagramAccount;
    this.twitterAccount = twitterAccount;
    this.githubAccount = githubAccount;
}

But I don’t like that solution either. The instantiation would be quite unreadable and we would have to fill some parameters with null.

Person p = new Person(
    "Steve Gates",
    LocalDate.of(1980, 12, 25),
    "stevegates@gmail.com",
    "1234598763",
    null,
    "sgates",
    "steve_gatez",
    null,
    "stevegates"
);

Without looking to the constructor definition, would you be able to tell which Instagram account is used above or what are the null attributes of the instance? I wouldn’t.

The Builder pattern solves that problem by helping us make our code simpler and more readable.

There are many variations of implementation, but in theory, they all do the same. I’ll show you how I normally do it.

Implementing the Builder Pattern

Let’s start by creating a static nested class in Person class called Builder with the same attributes as Person class and, for each attribute, we’ll create a method to set constructor values that returns the Builder instance. I normally name the methods with the prefix “with” before the attribute’s name, but we can often see people using “set” or even only the attribute’s name – withEmail(String), setEmail(String) or email(String).

public class Person {

    (...)
    public static class Builder {
        private String name;
        private LocalDate birthDate;
        private String email;
        private String phoneNumber;
        private String website;
        private String facebookAccount;
        private String instagramAccount;
        private String twitterAccount;
        private String githubAccount;

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

        public Builder withBirthDate(LocalDate birthDate) {
            this.birthDate = birthDate;
            return this;
        }

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

        public Builder withPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
            return this;
        }

        public Builder withWebsite(String website) {
            this.website = website;
            return this;
        }

        public Builder withFacebookAccount(String facebookAccount) {
            this.facebookAccount = facebookAccount;
            return this;
        }

        public Builder withInstagramAccount(String instagramAccount) {
            this.instagramAccount = instagramAccount;
            return this;
        }

        public Builder withTwitterAccount(String twitterAccount) {
            this.twitterAccount = twitterAccount;
            return this;
        }

        public Builder withGithubAccount(String githubAccount) {
            this.githubAccount = githubAccount;
            return this;
        }

    }

    (...)
}    

Now in Person class, we’ll create a static method that will construct a Builder instance:

public class Person {

    (...)

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

}

Then we’ll change the constructor of Person class to:

private Person(Builder builder) {
    this.name = builder.name;
    this.birthDate = builder.birthDate;
    this.email = builder.email;
    this.phoneNumber = builder.phoneNumber;
    this.website = builder.website;
    this.facebookAccount = builder.facebookAccount;
    this.instagramAccount = builder.instagramAccount;
    this.twitterAccount = builder.twitterAccount;
    this.githubAccount = builder.githubAccount;
}

And finally, we’ll create the following method in Builder class:

public Person build() {
    return new Person(this);
}    

Now we have our Builder ready to use. Let’s change our method in PeopleApi class to return a List<Person>:

@Path("/people")
@Produces(MediaType.APPLICATION_JSON)
public class PeopleApi {

    @GET
    public Response get() {
        final List  people = List.of(
            Person.builder()
                    .withName("Steve Gates")
                    .withBirthDate(LocalDate.of(1980, 12, 25))
                    .withEmail("stevegates@gmail.com")
                    .withPhoneNumber("1234598763")
                    .withFacebookAccount("sgates")
                    .withInstagramAccount("steve_gatez")
                    .withGithubAccount("stevegates")
                    .build()
        );

        return Response.ok(people).build();
    }

}    

Note that we can now easily identify which value will be set in each attribute. Also, we don’t have to set the null ones.

So let’s test our application running the following command:

./mvnw compile quarkus:dev

After the build, the output should look like this:

Listening for transport dt_socket at address: 5005
__  ____  __  _____   ___  __ ____  ______
--/ __ / / / / _ | / _ / //_/ / / / __/
-/ /_/ / /_/ / __ |/ , _/ ,< / /_/ / 
--________/_/ |_/_/|_/_/|_|____/___/
2020-06-21 17:37:19,439 INFO  [io.quarkus] (Quarkus Main Thread) builder-pattern 1.0-SNAPSHOT on JVM (powered by Quarkus 1.5.2.Final) started in 1.208s. Listening on: http://0.0.0.0:8080
2020-06-21 17:37:19,460 INFO  [io.quarkus] (Quarkus Main Thread) Profile dev activated. Live Coding activated.
2020-06-21 17:37:19,461 INFO  [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, resteasy-jsonb]    

Your application is up and waiting for requests. So let's make a GET request:

curl http://localhost:8080/people    

We should receive the List we created in PeopleApi class:

[{"birthDate":"1980-12-25","email":"stevegates@gmail.com","facebookAccount":"sgates","githubAccount":"stevegates","instagramAccount":"steve_gatez","name":"Steve Gates","phoneNumber":"1234598763"}]

It works!

That's it. Using the Builder Pattern we could create an object in a fancy, fluent, and readable way.

If you want to read more about that and other Design Patterns, I recommend the book Head First Design Patterns: A Brain-Friendly Guide and the classic and essential Effective Java.

You can find this source code in GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *