Design Pattern :  Builder

Design Pattern : Builder

Introduction

Design patterns are generic solutions to classic problems encountered in software development and many other fields. As a software developer, knowing them often means having a good grounding in computer science. Of course, it's not enough to be able to explain all design patterns (which in itself is a powerful asset), you also need to be able to use them in real-life situations. In this short article, which is the first in a series I'd like to dedicate to design patterns, we'll be talking about one of the object construction patterns: the Builder pattern. We'll be doing a java implementation.

What is the problem ?

Have you ever done things like this ?

UserProfile userProfile = new UserProfile(
                "namek", 
                "email@mail.fr", 
                "password", 
                "ntepp", 
                0, 
                null, 
                null, 
                null, 
                null, 
                null
);

Then you've been given the opportunity to use the design pattern builder. In fact, in many designs, we end up with objects with many fields and sub-objects, not all of which are necessary at any given time, as illustrated in the previous case. Here, we can see that of all the fields in UserProfile, only 4 are required at the time of construction. And yet the constructor is overloaded with null values for optional fields.

In addition to mandatory informations such as username, email, pasword, name, you may need other fields such as: address (which is an object), phone, interest (a list), friend (a list), job, number of children, ... You can quickly see that when you first initialize such a user, only a few fields will be useful. The others will be added as and when required. How can this be implemented? One approach would be to have a constructor that takes all these fields as input, setting to null any values not yet available. This works well, but results in very poor quality code. Another approach would be to refactor the code by creating subclasses that extend the first class. For example, the parent class would be User, which would only have fields common to all humans.

The builder pattern in practice

The builder pattern is designed to allow complex objects to be built in several stages, using only the necessary fields. So be it!

The idea is to delegate the creation of such an object to a subclass, enabling it to create the object in several stages.

Let's implement the case of our UserProfile. We define the class as follows with a private constructor to avoid class to be instanciated.

public class UserProfile {
    private String username;
    private String email;
    private String password;
    private String name;
    private int age;
    private String address;
    private String phoneNumber;
    private List<String> interests;
    private List<String> friends;
    private List<String> groups;

    private UserProfile(Builder builder) {
        this.username = builder.username;
        this.email = builder.email;
        this.password = builder.password;
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
        this.phoneNumber = builder.phoneNumber;
        this.interests = builder.interests;
        this.friends = builder.friends;
        this.groups = builder.groups;
    }
    //TODO: Create a public static Builder class
}

Now we delegate de construction to a public and static inner class that we call Builder. So replace the previous TODO comment with the following code.

    public static class Builder {
        private String username;
        private String email;
        private String password;
        private String name;
        private int age;
        private String address;
        private String phoneNumber;
        private List<String> interests;
        private List<String> friends;
        private List<String> groups;

        // Constructeur avec les champs obligatoires
        public Builder(String username, String email, String password, String name) {
            this.username = username;
            this.email = email;
            this.password = password;
            this.name = name;
        }

        // Méthodes pour définir les champs optionnels
        public Builder age(int age) {
            this.age = age;
            return this;
        }
        public Builder address(String address) {
            this.address = address;
            return this;
        }

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

        public Builder interests(List<String> interests) {
            this.interests = interests;
            return this;
        }

        public Builder friends(List<String> friends) {
            this.friends = friends;
            return this;
        }

        public Builder groups(List<String> groups) {
            this.groups = groups;
            return this;
        }


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

if you have mandatory fields in your class, allow the constructor of the inner class to initialize them. Create a method for each optional field.

You can now build objects of this type according to your actual needs.

// if you only need mandatory properties
UserProfile userProfile = new UserProfile
            .Builder("ntepp", "email@mail.fr", "ntepp", "joel")
            .build();
//if you also need to set age and address
UserProfile userProfile = new UserProfile
            .Builder("ntepp", "email@mail.fr", "ntepp", "joel")
            .age(10)
            .address("00 lorem ipsum, 0000 lorem")
            .build();

Using Lombok's @Data annotation

Once you've understood how this works and know how to implement it yourself, you can use the Lombok @Builder annotation, which, thanks to the SOURCE retention policy, will add these extra lines of code to your class to enable it to implement the builder pattern.

Personally, I always recommend that you understand the fundamental concepts before using frameworks or pre-built solutions.

So the following is the Lombok equivalent :

@Builder
public class UserProfile {
    private String username;
    private String email;
    private String password;
    private String name;
    private int age;
    private String address;
    private String phoneNumber;
    private List<String> interests;
    private List<String> friends;
    private List<String> groups;
}

No fields are mandatory in this case. So no constructor will be called during instantiation.

UserProfile userProfile = new UserProfile
            .builder()
            .username("namek")
            .email("email@mail.fr")
            .age(10)
            .address("00 lorem ipsum, 0000 lorem")
            .build();

Conclusion

We've studied what the pattern builder is, when to use it and how to implement it in Java. We've also seen how to use Lombok's @Builder annotation to produce much the same result. It's up to you to try it out and give me your feedback in the comments.

References :