Builder pattern revisited

TL;DR: This is the first part of the series dedicated to builder pattern. Besides the indepth explanation of the pattern it contains two rarely used techniques, which you may not know even if you are an experienced developer. While I encourage to read the whole article, you may also want to jump directly to removing field redundancy and fluent builder.

„The simplest things are the hardest to understand.”
― Kevin Wilson, The Family Fang

Many of us when asked about design patterns, during an interview for instance, tend to think that it is a boring and tedious question. After all, we have been told so many times about them that they grow in our minds to some unreachable, mythical beasts, which seem to be quite nice when being read about, but do not have much to do with real life (or real coding in that matter).

The reason for that is the fact that without a context most of design patterns do not seem practical at first and it is not easy to see what kind of problems they really try to solve. Moreover, in great number of sources, we see the same trivial implementation over and over again. So we know about them, we read about them but in the end we quickly forget.

An example of an underestimated pattern is the builder pattern. I had the idea to write about it for quite some time in the back of my mind, but only after really going into it I realized that the topic is actually more complex than I originally had thought. Because of this there will be at least two articles about builder pattern – the first one focusing on its main advantages and implementation details and the second one discussing various use cases in production and test code.

The idea behind builder pattern is actually quite simple – it should help you to build your objects. But let me show you today why it is so important to use and how we can adjust it with some implementation tricks to achieve different goals and coding styles.

Anemic model and builder antipattern

The hero of today is a simplistic User class, which can be found in many web application domain models. It will be refactored couple of times during the course of the article but the initial version looks like below:

public class User {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    // getters and setters omitted for brevity

}

The first thing to notice is that this class is badly encapsulated. There are setters which allow to change the internal state of User without any restrictions. The second thing that can turn someones attention is the possibility of login, password and email being required fields and the rest of the fields being optional. What is bad is that these requirements are not visible in the code itself. Having such a class at hand one can even construct the following monster:

User mrNobody = new User();
mrNobody.setLogin("");
mrNobody.setPassword("");
mrNobody.setEmail("");
mrNobody.setName("");
mrNobody.setAge(null);

It is hardly though to blame Mr Nobody for how he is – someone just made him so and now he does not even represent a User – he actually represents nothing. This example is of course a bit artificial, but it is not so hard to imagine production code in which someone adds a new method accepting domain object and calling innocent looking setter on it only to realize that it breaks logic couple of calls later. What is really terrifying though is that more then couple of times I have seen situations in which some would like to have a builder for it and apparently populate Earth with army of Nobodies. Such a builder would have the form of:

public class UserBuilder {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    public UserBuilder setLogin(String login) {
        this.login = login;
        return this;
    }

    public UserBuilder setPassword(String password) {
        this.password = password;
        return this;
    }

    // other setters ommitted for brevity

    public User build() {
        User user = new User();
        user.setLogin(login);
        user.setPassword(password);
        user.setEmail(email);
        user.setName(name);
        user.setSurname(surname);
        return user;
    }

}

It is crucial to realise, that implementation like that does not serve any purpose – it does not help to construct the object and it does not help to keep domain invariants. In the end it is just duplicated code – it does not do anything more than the original class does. Of course we can do way better – both with the User class and the builder class itself.

Let it build

There are two main problems with the User class:

  1. We can easily create damaged objects. Mr Nobody is a perfect example here.
  2. We can easily damage valid objects after they were created. Imagine for instance John, who is perfectly healthy instance of the User class – he has his login, password and email (he actually even has a loving wife and a dog!). But one day there comes a programmer who calls some setters and rips him off his identity turning him into Mr Nobody…

Let us deal with the second issue first and the solution for both of them will come naturally.

Getting rid of setters

The first approach towards the goal may consist of removing setters and equipping the object with constructor assigning all the fields:

public class User {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    public User(String login, String password, String email, 
                String name, Integer age) {
        this.login = login;
        this.password = password;
        this.email = email;
        this.name = name;
        this.age = age;
    }

    // getters omitted for brevity

}

Now, because our class does not have setters anymore, we have dealt with the possibility of making the internal state damaged after creating the object, but this solution has still one drawback – it does not emphasize which fields are required and which are optional. We can comment them appropriately in the code, but this would still force someone to call the constructor like this:

new User("john91", "qwerty", "john91@mail.com", null, null);

Explosion of constructors

One possibility to deal with the ugly call presented above is to add all kinds of constructors dealing with the existence of optional fields:

public User(String login, String password, String email) {
    this(login, password, email, null, null);
}

public User(String login, String password, String email,
            String name) {
    this(login, password, email, name, null);
}

public User(String login, String password, String email,
            Integer age) {
    this(login, password, email, null, age);
}

public User(String login, String password, String email,
            String name, Integer age) {
    this.login = login;
    this.password = password;
    this.email = email;
    this.name = name;
    this.age = age;
}

It may do the trick, but in general it is just too much code, it does not scale very well and it is not applicable in every case.

Imagine you will be asked to add another two fields to the User class – say gender and height for instance. It would require sixteen constructors to deal with all optional fields combinations. What is more replacing current age field with surname field of type String would make it even impossible to still use four constructors like above, because two of them (one with required fields and name and second one with required fields and surname) will have the same signature, so the compiler will complain.

Delegating the work

In the real life if you would struggle with so many problems you would probably consider hiring a professional. And guess what – in this situation builder pattern is the best one you can get.

The most obvious implementation assumes getting rid of all constructors except the one accepting all the fields and introducing new class:

public class UserBuilder {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    public static UserBuilder user(String login, String password, String email) {
        return new UserBuilder(login, password, email);
    }

    private UserBuilder(String login, String password, String email) {
        this.login = login;
        this.password = password;
        this.email = email;
    }

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

    public UserBuilder withAge(Integer age) {
        this.age = age;
        return this;
    }

    public User build() {
        validate();
        return new User(login, password, email, name, age);
    }

    private void validate() {
        validateRequiredField("login", login);
        validateRequiredField("password", password);
        validateRequiredField("email", email);
    }

    private void validateRequiredField(String fieldName, String value) {
        if(isNull(value) || value.isEmpty()) {
            throw new IllegalStateException("Invalid state, field [" +
                    fieldName + "] may not be null or empty.");
        }
    }

}

The usage can be for instance:

User john = UserBuilder.user("john91", "qwerty", "john91@mail.com")
                .withName("John")
                .build();

Now we have properly showed to someone willing to build instance of the User class which fields are required and which are optional. Additionally we do not allow situations in which any of the required fields will be null or empty String – if such situation would occur the exception will be thrown during call to the build method. As a result we greatly reduced possibility of the existence of damaged User objects in our code.

The only possibility to build Mr Nobody now (apart from using reflection of course) would be to call the constructor of User class directly. We can deal with it though pretty easily.

Attentive readers may have noticed one thing. Instead of implementing the builder we could just modify the User class by adding constructor for all the required fields and setters for the ones we allow to modify. In some cases it will be perfectly fine. But as usual it also depends on the context.

The situations in which builder might be a better choice are for instance:

  • when our intention is to build immutable objects – although we can throw an exception from setters upon subsequent calls, builder pattern provides much cleaner and simpler solution for immutability
  • when two or more fields in our domain model are mutually dependent (i.e. setting one requires from the second one to not have defined value) – such dependencies may be sometimes necessary and it is much easier to deal with them within the isolated environment for object construction provided by builder

Keeping it close

So you have your highly trained and skillful professional able to do the work and build the proper User object. But there is still a flaw in this beautiful picture – the constructor of the User class is public so someone can come and try to build the object on its own filling any of required fields with empty String or null. What is worst it seems there is nothing you can do about it. Or do you?

Well, actually there is a way – you need to keep the builder class closer to the object it helps to build and strengthen access modifier of this objects constructor. There are at least two possibilities to achieve this:

  1. Changing the accessibility modifier of the constructor to the default one (so no modifier at all) and keeping the builder within the same package as the User class
  2. Changing the constructor to private and implementing the builder as a nested static class of the User itself

The obvious drawback of the first approach is the fact that classes from the same package will still be able to do nasty things. Although it is not really a problem in many cases a much better approach is the second one. The implementation would look like this:

public class User {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    private User(String login, String password, String email, 
                 String name, Integer age) {
        this.login = login;
        this.password = password;
        this.email = email;
        this.name = name;
        this.age = age;
    }

    // getters omitted for brevity


    public static class UserBuilder {

        // builder implementation omitted for brevity

    }

}

With this little improvement we made sure that anyone willing to have instance of User class will have to use our newly created builder. The only difference in builder usage is that now we need to reference it through the User class, but one static import will make it invisible.

You don’t need to duplicate fields!

One thing that certainly looks tedious and redundant is the existence of the same fields in the builder class as in the object it actually builds. This is not a big problem with User class, consisting of only five fields, but could become annoying for more complex objects. Luckily there is remedy for this, which leverages the fact that our nested builder can directly access the fields of enclosing User class. The code below shows the implementation without duplicated fields:

public class User {

    private String login;
    private String password;
    private String email;

    private String name;
    private Integer age;

    private User() {}

    // getters omitted for brevity

    public static class UserBuilder {

        private List<Consumer<User>> operations;

        public static UserBuilder user(String login, String password, String email) {
            UserBuilder builder = new UserBuilder();
            builder.operations.add(user -> user.login = login);
            builder.operations.add(user -> user.password = password);
            builder.operations.add(user -> user.email = email);
        }

        private UserBuilder() {
            this.operations = new ArrayList<>();
        }

        public UserBuilder withName(String name) {
            operations.add(user -> user.name = name);
            return this;
        }

        public UserBuilder withAge(Integer age) {
            operations.add(user -> user.age = age);
            return this;
        }

        public User build() {
            User user = new User();
            operations.forEach(operation -> operation.accept(user));
            validate(user);
            return user;
        }

        // validation code omitted for brevity

    }

}

There are two things to notice. First of all, instead of keeping fields in the builder class, now we keep only list of operations which should be applied to an empty User instance during build time. Second of all, we resigned from keeping all fields constructor of User class and kept only no args private one. This does not affect the way one can operate with any of the classes – in our previous solution constructor was private anyway.

In the examples above the validation part of the code was deliberately placed inside the builder. It allowed us to keep only parameterless constructor in the User class. As the result we were able to remove duplicated fields from the builder implementation and replace them with list of operations being applied to the user.

Although traditionally it is the role of the constructor to create valid objects it is worth to notice that in this particular case the builder took its role. We have hidden the constructor making it private and the only entry point for creating instances of User class is UserBuilder.

One side note is that the implementation above uses Java 8 and its functional constructs. Those who are not so lucky to be able to use Java 8 still can achieve the same effect though. There are two possible solutions presented in the appendix to this article.

Let your IDE help you build your objects

Last implementation still suffers from the necessity of having the initial call which enforces all the required fields to be specified. There are two problems with this:

  1. All the fields are of the same type which forces the client of the code to constantly remember in which order he should supply them
  2. It is OK for three required fields of User class to be set like this but it will become cluttered for more complex objects with more required fields.

Facing such problems may tempt to weaken the contract of the builder and divide the initial call into individual builder methods like this:

public static class UserBuilder {

        private List<Consumer<User>> operations;

        public static UserBuilder user() {
            return new UserBuilder();
        }

        private UserBuilder() {
            this.operations = new ArrayList<>();
        }

        public UserBuilder withLogin(String login) {
            operations.add(user -> user.login = login);
            return this;
        }

        public UserBuilder withPassword(String password) {
            operations.add(user -> user.password = password);
            return this;
        }

        public UserBuilder withEmail(String email) {
            operations.add(user -> user.email = email);
            return this;
        }

        // ...

}

While this may seem like a fair tradeoff for calling the overloaded initial method it is actually immensely wrong. It is true that we are still safe in runtime and nobody will be able to attempt creating damaged User object without raising an exception. But at the same time we give no clue to clients of our code which fields are required, making following calls to look innocent:

User john = UserBuilder.user()
        .withLogin("john91")
        .withAge(26)
        .build();

User anne = UserBuilder.user()
        .withName("Anne")
        .withEmail("anne94@mail.com")
        .withAge(23)
        .build();

After all, if someone allowed me to do such calls then it must be OK. At least I would think so seeing such an API for a builder and I would be quite surprised getting exception in runtime.

This implementation is actually very close to the antipattern from the first paragraph – the thin line between them is existence or lack of the validation. And it is easy to forget about validating something and live in an illusion of nicely encapsulated classes while at the same time exposing API for mutating them in every possible way…

What can we do about it then? The easiest way would be to keep the initial call but reduce its complexity by introducing wrapper objects or value objects at least. Much more interesting though would be to keep the style of single argument builder methods and somehow force the programmer to use all the required ones. In other words – arranging a situation in which after calling the first builder method the programmer would be left with only one choice for the next call. Believe me or not but this is possible. Take a look at the following interfaces:

public interface UserLoginBuilder {

    UserPasswordBuilder withLogin(String login);

}

public interface UserPasswordBuilder {

    UserEmailBuilder withPassword(String password);

}

public interface UserEmailBuilder {

    UserOptionalFieldsBuilder withEmail(String email);

}

public interface UserOptionalFieldsBuilder {

    UserOptionalFieldsBuilder withName(String name);

    UserOptionalFieldsBuilder withAge(Integer age);

    User build();

}

We have aggregated all optional calls and the build method in one interface and all required calls in separate interfaces. The idea is to make UserBuilder class to implement all of those and use them in a clever way reducing the number of possible paths which can be taken by programmer during chaining the calls. The builder class does not change a lot:

public static class UserBuilder implements 
    UserLoginBuilder, UserPasswordBuilder, 
    UserEmailBuilder, UserOptionalFieldsBuilder {

    private List<Consumer<User>> operations;

    public static UserLoginBuilder user() {
        return new UserBuilder();
    }

    private UserBuilder() {
        this.operations = new ArrayList<>();
    }

    @Override
    public UserPasswordBuilder withLogin(String login) {
        operations.add(user -> user.login = login);
        return this;
    }

    @Override
    public UserEmailBuilder withPassword(String password) {
        operations.add(user -> user.password = password);
        return this;
    }

    @Override
    public UserOptionalFieldsBuilder withEmail(String email) {
        operations.add(user -> user.email = email);
        return this;
    }

    @Override
    public UserOptionalFieldsBuilder withName(String name) {
        operations.add(user -> user.name = name);
        return this;
    }

    @Override
    public UserOptionalFieldsBuilder withAge(Integer age) {
        operations.add(user -> user.age = age);
        return this;
    }

    public User build() {
        User user = new User();
        operations.forEach(operation -> operation.accept(user));
        validate(user);
        return user;
    }

    // validate and validateRequiredField methods are the same as in the previous impl

}

What is truly great about the implementation above is the fact that now method completion in any IDE will actually guide the programmer through the object creation process. It will prompt him with proper method at the given time:

Moreover the compiler would not allow to execute build method without specifying all the required fields while at the same time it will allow to skip all optional parameters. So the shortest possible use case is now enforcing the first three calls:

UserBuilder.user() // returns UserLoginBuilder
        .withLogin("john91") // returns UserPasswordBuilder
        .withPassword("qwerty") // returns UserEmailBuilder
        .withEmail("john91@mail.com") // returns UserOptionalFieldsBuilder
        .build();

I like to call this kind of implementation a fluent builder because it resembles a lot fluent interfaces which we can spot from time to time in libraries nowadays.

Of course the obvious drawback of such approach is the amount of boilerplate code that needs to be added for the fluent builder to work like expected. So it is rather too much of an effort for such a simple class like the one in the example here. It pays off however when we know we will use the builder extensively. The natural candidates for such coding style seem to be database query objects, request builders for complex remote APIs and complex domain objects when we tend to have rich model in our application.

Summary

Builder pattern, which seems to be simple and well established, is very commonly underestimated and not understood properly nowadays. It is not rare to see this pattern being implemented for classes representing anemic domain model, which expose getters and setters for all theirs fields. While having such a model may be justified in some cases it is crucial to realise that implementing builder for these classes creates only duplicated code, which does not serve any purpose, and should be therefore considered as antipattern.

The true power of the builder pattern comes from the following facts:

  • it enables well encapsulated objects to be built in a controlled manner
  • it greatly reduces possibility of having damaged objects (i.e. objects with inconsistent inner state) in runtime, thus reducing amount of code needed to deal with such anomalies
  • it simplifies creation of complex objects for the clients of our code (sometimes even guiding them through the entire process with application domain specific language)

While the above facts may be well know it is worth to realise that when it comes to implementation there is a lot we can do with the bare builder pattern examples, which can be found in literature. During the course of this article we have:

  • placed the builder inside the class it helps to build, which allowed us to better encapsulate the User object
  • removed all the duplicated fields from the builder, which was possible due to Java 8 functional constructs but can be achieved also in previous versions of Java
  • introduced marker interfaces, which have made from the typical pattern implementation the one called fluent builder being able (with the help of IDE) to guide programmers through the process of creating objects

I hope that I have properly emphasized the strengths of builder pattern, showed that it is not at all boring with the help of some implementation tricks and in the end inspired you to use some of these solutions in your projects. Happy coding!


Appendix

If for some reasons you can’t use Java 8 you can easily replace constructs presented in You don’t need to duplicate fields! with one of the following approaches:

  • creating interface resembling Consumer from Java 8 and using anonymous classes instead of lambda expressions:
private interface BuilderOperation {
	void apply(User user);
}

public static class UserBuilder {
	
	private List<BuilderOperation> operations;     
	
	// ...     
	public UserBuilder withName(String name) {         
		operations.add(new BuilderOperation() {             
			@Override             
			public void apply(User user) {                 
				user.name = name;             
			}        
		});         
		return this;    
	}          
	// ...      
}
  • keeping empty instance of User class directly in builder and setting its fields during build time:
public static class UserBuilder {     

    private User user;     

    // ...     
    
    private UserBuilder() {         
        user = new User();     
    }     

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

    // ... 

}

Related Post

2 response to "Builder pattern revisited"

  1. By: Mike Teplitskiy Posted: November 21, 2017

    Good article. Very practical.
    Please check my web site for more ideas how Builders can be used asynchronously in REACTIVE fashion.

    • By: Mateusz Fedkowicz Posted: November 22, 2017

      Thanks a lot! I will definitely check your site.

Leave a Reply

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