Thursday, February 5, 2009

Builder Pattern

Joshua Bloch discusses the Builder Pattern in the second edition of his book Effective Java.  Use of the Builder pattern avoids the JavaBean pattern and the 'telescoping constructor' for classes with many fields.  The JavaBean pattern can cause the following problems:

1. The parameterless constructor and the setter methods of the JavaBean pattern do not enforce the provision of all necessary fields.
2. The JavaBean pattern encourages the creation of instances which can be left in an incomplete state.
3. The 'telescoping' (many parameters) constructor is often difficult to decipher and can be the source of bugs.
4. Setters violate encapsulation of logic.
5. Setters make objects mutable.

Maintainability is improved by eliminating the parameterless constructor and the multitude of setters and by eliminating the many parameter constructor.  The Builder class accepts each of the subject class's values in an explicit manner that mimics the named parameters style rather than the implicit form of the many parameter constructor.

Note the static inner class, Builder.  The outer class, the business component, has a private constructor that accepts an instance of the Builder inner class.  In this constructor, values stored in the fields of the builder are used to populate the fields of the outer class.  The use of the builder requires that first, each field is set then the build method is called and it returns the new instance'. Here's an example of a Builder.

ProductNutrition campbellsTomatoSoup = new ProductNutrition.Builder() .servingSize(1000) .calories(140) .gramsOfTotalFat(1) .gramsOfSaturatedFat(1) .gramsOfTransFat(0) .gramsOfFiber(7) .milligramsOfCholesterol(5) .milligramsSodium(440) .gramsCarbohydrate(25) .build();

import org.apache.commons.lang.builder.ToStringBuilder; public class ProductNutrition { private final int gramsServingSize; private final int calories; private final int gramsOfTotalFat; private final int gramsOfSaturatedFat; private final int gramsOfTransFat; private final int gramsOfFiber; private final int milligramsOfCholesterol; private final int milligramsSodium; private final int gramsCarbohydrate; private ProductNutrition(Builder builder) { gramsServingSize = builder.gramsServingSize; calories = builder.calories; gramsOfTotalFat = builder.gramsOfTotalFat; gramsOfSaturatedFat = builder.gramsOfSaturatedFat; gramsOfTransFat = builder.gramsOfTransFat; gramsOfFiber = builder.gramsOfFiber; milligramsOfCholesterol = builder.milligramsOfCholesterol; milligramsSodium = builder.milligramsSodium; gramsCarbohydrate = builder.gramsCarbohydrate; } public String toString() { return new ToStringBuilder(this) .append("serving size", gramsServingSize) .append("calories", calories) .append("grams of total fat", gramsOfTotalFat) .append("grams of saturated fat", gramsOfSaturatedFat) .append("grams of transfat", gramsOfTransFat) .append("grams of fiber", gramsOfFiber) .append("milligrams of cholesterol", milligramsOfCholesterol) .append("milligrams sodium", milligramsSodium) .append("grams of carbohydrate", gramsCarbohydrate) .toString(); } static class Builder { private int gramsServingSize; private int calories; private int gramsOfTotalFat; private int gramsOfSaturatedFat; private int gramsOfTransFat; private int gramsOfFiber; private int milligramsOfCholesterol; private int milligramsSodium; private int gramsCarbohydrate; public Builder servingSize(int val) { gramsServingSize = val; return this; } public Builder calories(int val) { calories = val; return this; } public Builder gramsOfTotalFat(int val) { gramsOfTotalFat = val; return this; } public Builder gramsOfSaturatedFat(int val) { gramsOfSaturatedFat = val; return this; } public Builder gramsOfTransFat(int val) { gramsOfTransFat = val; return this; } public Builder gramsOfFiber(int val) { gramsOfFiber = val; return this; } public Builder milligramsOfCholesterol(int val) { milligramsOfCholesterol = val; return this; } public Builder milligramsSodium(int val) { milligramsSodium = val; return this; } public Builder gramsCarbohydrate(int val) { gramsCarbohydrate = val; return this; } public ProductNutrition build() { return new ProductNutrition(this); } } }


The Builder pattern makes it apparent to which value each argument is applied thus making the code more comprehensible and maintainable.  Bloch suggests that the Builder be given a multiple parameter constructor that accepts each required parameter.  In this way, the Builder will ensure that the object is not left in an incomplete state.  The alternative description of the Builder pattern in 'Applied Java Patterns' suggests that if not all required parameters are supplied that InformationRequiredException be thrown.

The Builder patterns as described in 'Applied Java Patterns' does not seem to have the advantages of Bloch's implementation. These two examples of the use of this implementation of Builder expose the complexity of a multiple parameter constructor to the user of the class. This does not seem to be the most preferable solution and I prefer Bloch's version.

System.out.println("Creating a new Appointment with an AppointmentBuilder"); appt = pimScheduler.createAppointment( apptBuilder, createDate(2066, 9, 22, 12, 30), null, "Trek convention", new LocationImpl("Fargo, ND"), createAttendees(4)); System.out.println("Successfully created an Appointment.");

System.out.println("Creating a new Appointment with a MeetingBuilder"); System.out.println("(This time, the MeetingBuilder will provide an end date)"); try{ appt = pimScheduler.createAppointment( mtgBuilder, createDate(2002, 4, 1, 10, 00), createDate(2002, 4, 1, 11, 30), "OOO Meeting", new LocationImpl("Butte, MT"), createAttendees(2)); System.out.println("Successfully created an Appointment."); } catch ....