📜 ⬆️ ⬇️

Elegant Java Builder

Most likely, most experienced programmers are familiar with the Pattern Builder. It allows you to make the initialization of data structures more visual, flexible while maintaining such a useful feature as immutability. Here is a classic example from the first page of Google’s issue of the query “java builder pattern example”. With all its advantages, the main disadvantage of this implementation of the pattern is two times more code, compared to the usual flat bean. If the generation of this additional code is not a problem for any popular IDE, then editing such a class becomes quite tiresome and readability suffers in any case.

At some point, I decided that I had to endure this and started looking for an alternative. I must say, the alternative was found quickly enough. In Java, there is a rarely used mechanism for non-static inner classes. An instance of such a class can only be created through an instance of the parent class using the .new operator. What is important, such an object has access to the private fields of its parent.

So, we have an immutable structure.

public class Account { private final String userId; private final String token; public Account(String token, String userId) { this.token = token; this.userId = userId; } public String getUserId() { return userId; } public String getToken() { return token; } } 

There are only two fields here now, but the builder would still be useful not to confuse the order of parameters in the constructor and if it is necessary to initialize only one field of two or both, but at different points in time. What can I say when the field becomes 20!
')
In order not to duplicate the fields in the building class, we simply start the inner class. He has access to the private fields of his parent class and can set them directly. We will make our own class constructor private, and remove the final modifier from the fields.

 public class Account { private String userId; private String token; private Account() { // private constructor } public String getUserId() { return userId; } public String getToken() { return token; } public class Builder { private Builder() { // private constructor } public Builder setUserId(String userId) { Account.this.userId = userId; return this; } public Builder setToken(String token) { Account.this.token = token; return this; } public Account build() { return Account.this; } } } 

The constructor of the builder is also private, otherwise having access to the Account instance could be done by the builder and through him change the fields of the already created object. The build method simply returns a ready object (here you can check whether all the required fields are in place, for example.

The final touch - add to the method to create an instance of the builder.

 public class Account { private String userId; private String token; private Account() { // private constructor } public String getUserId() { return userId; } public String getToken() { return token; } public static Builder newBuilder() { return new Account().new Builder(); } public class Builder { private Builder() { // private constructor } public Builder setUserId(String userId) { Account.this.userId = userId; return this; } public Builder setToken(String token) { Account.this.token = token; return this; } public Account build() { return Account.this; } } } 

Compare with traditional implementation:

 public class Account { private final String userId; private final String token; public Account(String userId, String token) { this.userId = userId; this.token = token; } public String getUserId() { return userId; } public String getToken() { return token; } public static class Builder { private String userId; private String token; public Builder setUserId(String userId) { this.userId = userId; return this; } public Builder setToken(String token) { this.token = token; return this; } public Account build() { return new Account(userId, token); } } } 

Try adding a new field or changing the type of the token field in either case. With an increase in the number of fields, the difference in the amount of code and readability will be increasingly noticeable. Let's compare the example from the article I referred to at the beginning of the topic (I changed it to match the styles of the examples):

 public class Person { private final String lastName; private final String firstName; private final String middleName; private final String salutation; private final String suffix; private final String streetAddress; private final String city; private final String state; private final boolean isFemale; private final boolean isEmployed; private final boolean isHomeOwner; public Person( final String newLastName, final String newFirstName, final String newMiddleName, final String newSalutation, final String newSuffix, final String newStreetAddress, final String newCity, final String newState, final boolean newIsFemale, final boolean newIsEmployed, final boolean newIsHomeOwner) { this.lastName = newLastName; this.firstName = newFirstName; this.middleName = newMiddleName; this.salutation = newSalutation; this.suffix = newSuffix; this.streetAddress = newStreetAddress; this.city = newCity; this.state = newState; this.isFemale = newIsFemale; this.isEmployed = newIsEmployed; this.isHomeOwner = newIsHomeOwner; } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } public String getMiddleName() { return middleName; } public String getSalutation() { return salutation; } public String getSuffix() { return suffix; } public String getStreetAddress() { return streetAddress; } public String getCity() { return city; } public String getState() { return state; } public boolean isFemale() { return isFemale; } public boolean isEmployed() { return isEmployed; } public boolean isHomeOwner() { return isHomeOwner; } public static class Builder { private String nestedLastName; private String nestedFirstName; private String nestedMiddleName; private String nestedSalutation; private String nestedSuffix; private String nestedStreetAddress; private String nestedCity; private String nestedState; private boolean nestedIsFemale; private boolean nestedIsEmployed; private boolean nestedIsHomeOwner; public Builder setNestedLastName(String nestedLastName) { this.nestedLastName = nestedLastName; return this; } public Builder setNestedFirstName(String nestedFirstName) { this.nestedFirstName = nestedFirstName; return this; } public Builder setNestedMiddleName(String nestedMiddleName) { this.nestedMiddleName = nestedMiddleName; return this; } public Builder setNestedSalutation(String nestedSalutation) { this.nestedSalutation = nestedSalutation; return this; } public Builder setNestedSuffix(String nestedSuffix) { this.nestedSuffix = nestedSuffix; return this; } public Builder setNestedStreetAddress(String nestedStreetAddress) { this.nestedStreetAddress = nestedStreetAddress; return this; } public Builder setNestedCity(String nestedCity) { this.nestedCity = nestedCity; return this; } public Builder setNestedState(String nestedState) { this.nestedState = nestedState; return this; } public Builder setNestedIsFemale(boolean nestedIsFemale) { this.nestedIsFemale = nestedIsFemale; return this; } public Builder setNestedIsEmployed(boolean nestedIsEmployed) { this.nestedIsEmployed = nestedIsEmployed; return this; } public Builder setNestedIsHomeOwner(boolean nestedIsHomeOwner) { this.nestedIsHomeOwner = nestedIsHomeOwner; return this; } public Person build() { return new Person( nestedLastName, nestedFirstName, nestedMiddleName, nestedSalutation, nestedSuffix, nestedStreetAddress, nestedCity, nestedState, nestedIsFemale, nestedIsEmployed, nestedIsHomeOwner); } } } 

And implementation through the inner class:

 public class Person { private String lastName; private String firstName; private String middleName; private String salutation; private String suffix; private String streetAddress; private String city; private String state; private boolean isFemale; private boolean isEmployed; private boolean isHomeOwner; private Person() { // private constructor } public String getLastName() { return lastName; } public String getFirstName() { return firstName; } public String getMiddleName() { return middleName; } public String getSalutation() { return salutation; } public String getSuffix() { return suffix; } public String getStreetAddress() { return streetAddress; } public String getCity() { return city; } public String getState() { return state; } public boolean isFemale() { return isFemale; } public boolean isEmployed() { return isEmployed; } public boolean isHomeOwner() { return isHomeOwner; } public static Builder newBuilder() { return new Person().new Builder(); } public class Builder { private Builder() { // private constructor } public Builder setLastName(String lastName) { Person.this.lastName = lastName; return this; } public Builder setFirstName(String firstName) { Person.this.firstName = firstName; return this; } public Builder setMiddleName(String middleName) { Person.this.middleName = middleName; return this; } public Builder setSalutation(String salutation) { Person.this.salutation = salutation; return this; } public Builder setSuffix(String suffix) { Person.this.suffix = suffix; return this; } public Builder setStreetAddress(String streetAddress) { Person.this.streetAddress = streetAddress; return this; } public Builder setCity(String city) { Person.this.city = city; return this; } public Builder setState(String state) { Person.this.state = state; return this; } public Builder setFemale(boolean isFemale) { Person.this.isFemale = isFemale; return this; } public Builder setEmployed(boolean isEmployed) { Person.this.isEmployed = isEmployed; return this; } public Builder setHomeOwner(boolean isHomeOwner) { Person.this.isHomeOwner = isHomeOwner; return this; } public Person build() { return Person.this; } } } 

It can be noted that, from the point of view of code organization, such a class differs from a regular flat bin with fields and heterosers only in that the setters are grouped in a separate inner class, only a couple of newBuilder () and build () methods are added, a line with the declaration of the inner class and private designers.

Important notes:

1. The build method of the builder returns the same object and if after its call to continue to expose the fields through the methods of the builder, the fields of the already created object will change. This is easy to fix if you create a new instance of the object each time:

 public Account build() { Account account = new Account(); account.userId = Account.this.userId; account.token = Account.this.token; return account; } 

This returns part of the duplicate code that we tried to get rid of. Usually the link to the builder does not leave the method, so I prefer the option that I showed first. If you often pass the builder back and forth and reuse it to re-generate objects, use the variant as shown above.

2. Thanks to the commentators, my doubts are dispelled - the object obtained from such a builder is not thread-safe due to the fact that the fields in it are not declared final. If this moment is important in your application, it is better to use the classic builder.

And finally - the use of the builder.

 Account account = Account.newBuilder() .setToken("hello") .setUserId("habr") .build(); 

so or

 Account.Builder accountBuilder = Account.newBuilder(); ... accountBuilder.setToken("hello"); ... accountBuilder..setUserId("habr"); return accountBuilder.build(); 

Again, Account.newBuilder () is dearer to my programmer eyes than new Account.Builder (), although this is a matter of taste.

All clean code!

UPD: As often happens on Habré, comments turned out to be more useful than the topic itself, recommended for familiarization.

Source: https://habr.com/ru/post/244521/


All Articles