I found an interesting blog post for using the builder pattern with mandatory values.
In short it uses the common builder pattern, but adds an twist by using interface to force which value to set next and thereby solving the problem with many mandatory values.
I will demonstrate the problem with the common builder and mandatory values here below.
public class PersonBuilder { private String name; private String lastName; private Date birthDate; private String phoneNumber; /** * Private constructor to force the use of the factroy method */ private PersonBuilder() { } /** * Creates a new person builder */ public static PersonBuilder aPerson() { return new PersonBuilder(); } public PersonBuilder withName(String aName) { name = aName; return this; } public PersonBuilder withLastName(String aLastName) { lastName = aLastName; return this; } public PersonBuilder withBirthDate(Date aBirthDate) { birthDate = aBirthDate; return this; } public PersonBuilder andPhoneNumber(String aPhoneNumber) { phoneNumber = aPhoneNumber; return this; } public Person build() { // The constructor and setters for Person has default scope // and is located in the same package as the builder Person p = new Person(); p.setName(name); p.setLastName(lastName); p.setBirthDate(birthDate); p.setPhoneNumber(phoneNumber); return p; } }
This code has many problems, first it allows the person to be built with no values at all. The user can simply do PersonBuiler.aPerson().build(). This will create a invalid person because name, last name and birth date are mandatory for the person. The second problem is that the user easily can forget to set one of the mandatory values and still create an invalid person.
We could force all mandatory values to be set in the aPerson method instead and make all with* methods private like this:
/** * Creates a new person builder */ public static PersonBuilder aPerson(String aName, String aLastName, Date aBirthDate) { return new PersonBuilder() .withName(aName) .withLastName(aLastName) .withBirthDate(aBirthDate); }
But this has another problem, when the number of mandatory values increase the readability will suffer and as Uncle Bob would say a method should not have more the 2 parameters. Also it’s easy to swap name and last name when calling this method when both of them are strings.
So to solve this we use the pattern describe in the blog post Builder pattern with a twist and get the following result.
public class PersonBuilder implements NamePersonBuilder, LastNamePersonBuilder, BirthDatePersonBuilder, FinalPersonBuilder { private String name; private String lastName; private Date birthDate; private String phoneNumber; /** * Private constructor to force the use of the factroy method */ private PersonBuilder() { } /** * Creates a new person builder */ public static NamePersonBuilder aPerson() { return new PersonBuilder(); } public LastNamePersonBuilder withName(String aName) { name = aName; return this; } public BirthDatePersonBuilder withLastName(String aLastName) { lastName = aLastName; return this; } public FinalPersonBuilder withBirthDate(Date aBirthDate) { birthDate = aBirthDate; return this; } public FinalPersonBuilder andPhoneNumber(String aPhoneNumber) { phoneNumber = aPhoneNumber; return this; } public Person build() { // The constructor and setters for Person has default scope // and is located in the same package as the builder Person p = new Person(); p.setName(name); p.setLastName(lastName); p.setBirthDate(birthDate); p.setPhoneNumber(phoneNumber); return p; } interface NamePersonBuilder { LastNamePersonBuilder withName(String aName); } interface LastNamePersonBuilder { BirthDatePersonBuilder withLastName(String aLastName); } interface BirthDatePersonBuilder { FinalPersonBuilder withBirthDate(Date aBirthDate); } interface FinalPersonBuilder { FinalPersonBuilder andPhoneNumber(String aPhoneNumber); Person build(); } }
This will force the user to set all mandatory values and also force the order that the values are set. So to construct a person this will be the resulting code:
PersonBuilder.aPerson() .withName("Name") .withLastName("LastName") .withBirthDate(new Date()) .build();
The only downside that I can see with this pattern is that the number of interface increases with the number of mandatory values. But I think it’s acceptable when the upside is readability and compile time check that all mandatory values are set.
Hey man, great article. I also used this pattern before, but I think I developed for Intellij a better alternative under the form of a plugin.
Feel free to check it out:
https://github.com/banterly91/Java-Builder-Guided-Completion-Intellij-Plugin