DEV Community

Cover image for Building objects like a pro!
Cristian Gonçalves
Cristian Gonçalves

Posted on

Building objects like a pro!

Design Patterns offers a lot of great approaches to solving recurrent and complex problems on a fancy and understandable way.

Throughout this article, I want to show you how and when you should use the Builder Pattern, so you can build your business logic's complex objects on an efficient and elegant way.

The Builder Pattern falls on the 'Creational Patterns' category. If you look about this Pattern on the web, you'll find several variants of it, depending on the specific problem you might want to solve. Today, I'll show you two common approaches: The Fluent Builder and the Facade Builder.

The Fluent Builder

I think that a good way to explain how a Fluent Builder works is by showing up the StringBuilder class in action.



    StringBuilder sb = new StringBuilder()

    sb.append("A ")
        .append("big ")
        .append("black ")
        .append("bug ")
        .append("bit ")
        .append("a ")
        .append("big ")
        .append("black ")
        .append("dog ")
        .append("on ")
        .append("his ")
        .append("big ")
        .append("black ")
        .append("nose!")

        println sb.toString()

By looking to StringBuilder's api, we realize that this class provides a fluent interface (chainable mechanism) for building long and complex strings by invoking the append() method consecutive times. This is achieved by returning the Builder class itself at the very end of the append() method:


    @Override
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

This is a very basic example, where the same method is invoked several times in order to build a complex string. Yet, the same mechanism should be used to call all the available Builder's methods, so the object can be seamlessly built.

The classical example

Let's imagine you need to build an instance of the Person class.


class Person {

    String firstName
    String lastName
    int age
    String personalEmail
    String country
    String city
    String address
    String houseNbr
    String job
    String company
    String jobEmail
    String jobAddress

    Person(String firstName, String lastName, int age, String personalEmail, String country, String city, String address, String houseNbr, String job, String company, String jobEmail, String jobAddress) {
        this.firstName = firstName
        this.lastName = lastName
        this.age = age
        this.personalEmail = personalEmail
        this.country = country
        this.city = city
        this.address = address
        this.houseNbr = houseNbr
        this.job = job
        this.company = company
        this.jobEmail = jobEmail
        this.jobAddress = jobAddress
    }

}

As you can see, the class itself has so many fields... and making a default constructor that receives all those required fields is just insane.

This approach just doesn't work. Any developer can mistakenly switch any of those fields during constructor invocation.

How can you solve this?! Fluent Builder pattern to the rescue!

Basically, with no surprises, you just have to provide a Person's Builder mechanism that must be used everytime anyone wants to get a Person's instance object.

To achieve this, you can simply implement a PersonBuilder class that holds all the required steps to build a Person object and returns the object itself at the very end of the process.

I rather have a static inner Builder class, inside the Person's class, to achieve the same result.

Let's do this!

First, we must assure that the Person class provides a default empty constructor to be used by the Builder, so it can start building the object. Be aware that this constructor must be declared as private so no one - outside the class's scope - can instantiate a Person's object through it!. Since our Builder class will live inside the Person's class scope, it will be able to access the constructor.


class Person {

(...)

private Person() {} // the empty private constructor to be accessed by the inner Builder class

}

Ok, now that we have the default private constructor, it's time to start building our Person's Builder.


class Person {

(...)

    static class Builder {
        Person p

        Builder() {
            p = new Person()
        }
    }

}
  • The Builder inner class is declared static so it can be easily accessible outside the Person's class scope (new Person.Builder())

  • The Builder class declares and initializes an empty Person's instance on its constructor that will be built along its builder methods.

The Builder's class skeleton is ready. Let's add the required steps (builder methods) so we can start building our Person's object:

class Person {

    (...)

    static class Builder {
        Person p

        Builder() {
            p = new Person()
        }


        Builder setFirstName(String aFirstName) {
            p.firstName = aFirstName
            return this
        }

        Builder setLastName(String aLastName) {
            p.lastName = aLastName
            return this
        }

        Builder setAge(int aAge) {
            p.age = aAge
            return this
        }

        Builder setPersonalEmail(String aPersonalEmail) {
            p.personalEmail = aPersonalEmail
            return this
        }

        Builder setCountry(String aCountry) {
            p.country = aCountry
            return this
        }

        Builder setCity(String aCity) {
            p.city = aCity
            return this
        }

        Builder setAddress(String aAddress) {
            p.address = aAddress
            return this
        }

        Builder setHouseNbr(String aHouseNbr) {
            p.houseNbr = aHouseNbr
            return this
        }

        Builder setJob(String aJob) {
            p.job = aJob
            return this
        }

        Builder setCompany(String aCompany) {
            p.company = aCompany
            return this
        }

        Builder setJobAddress(String aJobAddress) {
            p.jobAddress = aJobAddress
            return this
        }

        Builder setJobEmail(String aJobEmail) {
            p.address = aJobEmail
            return this
        }

        Person build() {
            return p
        }
    }

}


Notice that in order to provide a fluent interface, we need to return the Builder itself at the end of each step of the build process, except on the build() method, where we must return the (fully constructed) Person's object.

Again, the build() method should only be invoked when the object is completely built.

Using the Person::Builder

Now that our Builder is ready, let's see how we can use it to build a Person's object:


        Person person = new Person.Builder() // the empty Person's object is created
            .setFirstName("John")
            .setLastName("Doe")
            .setAge(32)
            .setPersonalEmail("john.doe@email.com")
            .setCountry("USA")
            .setCity("Chicago")
            .setAddress("Some street address")
            .setHouseNbr("Some house nbr")
            .setJob("Software Developer")
            .setCompany("Easy Soft dot com")
            .setJobEmail("john.doe@easy.com")
            .setJob("some job address")

            .build() // returns the fully constructed object

Regarding the first approach, with a default constructor that receives way many arguments, this is undoubtedly a more elegant and efficient approach. By exposing the Builder's api, it becomes easier to understand how to build the object.

However, we can make things even more interesting. On the use case above, we use a single Builder to build the object. But sometimes, your object is so complex, that you need to break down your Builder into multiple 'low level' Builders, that'll work in tandem to provide the required functionality to build the object.

I'm talking about the Facade Builder.

The Facade Builder

The Facade Builder owes its name to the fact of combining (who would tell) the Facade Pattern with the Builder Pattern, to merge multiple fluent interfaces into a single interface, to build very complex objects.

I'm aware that our Person object is not a so complex object... but I can fairly use it to show you how you can use this Builder Pattern variant to build Person's objects.

By looking into the Person class fields, let's see how many builders we can take from it.


class Person {

    // personal info
    String firstName
    String lastName
    int age
    String personalEmail

    // address
    String country
    String city
    String address
    String houseNbr

    // job profile
    String job
    String company
    String jobEmail
    String jobAddress
}


At first glance, it seems we can identify 3 distinct builders:

  1. A base Builder, containing the Person's name, age and personal email
  2. An Address Builder, responsible for holding the functionality to build the Person's address
  3. And finally, a Job Builder, responsible for building the Person's job profile.

So, how can we combine all those builder interfaces to build a single Person object?

By following the rules:

  • Have base Builder class (the facade interface)
    • The base Builder class must instantiate an empty object of the Person class
  • All other Builders must extend from the above base Builder
    • Those 'child Builders' must receive, on their constructor, a reference of the empty Person object created on the base Builder class
  • The base Builder class must provide functionality that allows from switching between the available builders.

The recipe is on the table. Time to start cooking the pattern!

We'll start with the Facade Builder (the required base Builder, remember?)



class Person {

    (...) 

    private Person() {}


    static class Builder {

        protected Person p

        protected Builder() {
            p = new Person()
        }

        Builder setFirstName(String aFirstName) {
            p.firstName = aFirstName
            return this
        }

        Builder setLastName(String aLastName) {
            p.lastName = aLastName
            return this
        }

        Builder setAge(int aAge) {
            p.age = aAge
            return this
        }

        Builder setPersonalEmail(String aPersonalEmail) {
            p.personalEmail = aPersonalEmail
            return this
        }

        AddressBuilder livesAt() {
            return new AddressBuilder(p)
        }

        JobBuilder worksAt() {
            return new JobBuilder(p)
        }

        Person build() {
            return p
        }
    }
}


  • As you can see, we keep the Person's constructor private so we assure that Person objects can only be built through the base Builder class.

  • Our base Builder class is an inner class of the Person class, so it can access Person's private empty constructor. It also declares the Person p field as protected, so it can only be accessed by the 'low level' (child) builders.

  • The base Builder class contains 2 important methods, livesAt() and worksAt(), that allow us to switch between the base Builder class and the AddressBuilder and JobBuilder classes during the object building process.

The Facade Builder is set, let's now create the AddressBuilderand JobBuilder classes



class AddressBuilder extends Person.Builder {

    AddressBuilder(Person person) {
        p = person
    }

    AddressBuilder setCountry(String aCountry) {
        p.country = aCountry
        return this
    }

    AddressBuilder setCity(String aCity) {
        p.city = aCity
        return this
    }

    AddressBuilder setAddress(String aAddress) {
        p.address = aAddress
        return this
    }

    AddressBuilder setHouseNbr(String aHouseNbr) {
        p.houseNbr = aHouseNbr
        return this
    }
}





class JobBuilder extends Person.Builder {

    JobBuilder(Person person) {
        p = person
    }

    JobBuilder setJob(String aJob) {
        p.job = aJob
        return this
    }

    JobBuilder setCompany(String aCompany) {
        p.company = aCompany
        return this
    }

    JobBuilder setJobAddress(String aJobAddress) {
        p.jobAddress = aJobAddress
        return this
    }

    JobBuilder setJobEmail(String aJobEmail) {
        p.jobEmail = aJobEmail
        return this
    }
}



  • The 'low level' builders extends from the base class

  • They also receive a reference of the empty Person object - from the base Builder class - on their constructors, so they can keep building the object from the moment they're invoked.

Things are looking good... it seems our Person's facade Builder is completely set up. Let's see it in action:


        Person person =
                new Person.Builder()
                        .setFirstName("John")
                        .setLastName("Doe")
                        .setAge(30)
                        .setPersonalEmail("john.doe@email.com")
                        .livesAt()
                            .setCountry("USA")
                            .setCity("Chicago")
                            .setAddress("10th Avenue")
                            .setHouseNbr("5400 N. Lakewood Avenue")
                        .worksAt()
                            .setJob("Software Developer")
                            .setCompany("Easy Soft dot com")
                            .setJobEmail("john.doe@easy.com")
                            .setJobAddress("12th Avenue")
                        .build()


Wow! This code looks pretty cooler than the last one.

By combining the available builders, we provided a facade interface that allows building Person's objects on a simpler and intuitive way. Also, by wrapping up the building steps into multiple categorized builders, we can provide a more organized api, giving some kind of guidance during the object's construction.

One thing I did not do was to add a validate() step within the build() method that assures that the object has all the required fields fulfilled before return it. This is a crucial step, yet (in my opinion) is one of the great caveats of these approaches.

Conclusion

Stop making complex objects with messy constructors and start using the Builder Pattern approach :)

Now that you know how to use the Builder Pattern, I hope that from now on, everytime you need to build objects, you can think on how this Pattern can help you on solving your problem.

Do you have any other useful info about this Pattern to share with us? Don't be shy, share it below :)

Stay tunned!

Top comments (0)