Background
In my previous post, I talked about creating objects using the Builder Pattern. I recommend that you read that article first, unless you are already familiar with the Builder Pattern.The Interface Builder Pattern
At the end of that post, I pointed out some areas of improvement and after experimenting a bit, I want to share a variant of the Builder Pattern with several advantages over the previously presented solutions that I call the Interface Builder Pattern. In the previous post, we had a Car that was built using the Builder Pattern where the Car was a concrete class. Now, if we instead let Car be an interface (or for that matter an abstract class) we can gain some important benefits:- The Car.Builder can select between many different implementations of Cars depending on what is appropriate at build() time.
- We can have one or several internal base classes implementing Cars that we can extend, both for the Builder itself and different implementations of Cars
- We can have cached Cars that we can provide instantly at build() time.
public interface Car { String getBrand(); String getType(); int getPower(); int getTorque(); int getGears(); String getColor(); static class Hidden { protected static class DefaultCar implements Car { // Required parameters private final String brand; private final String type; // Optional parameters protected int power; protected int torque; protected int gears; protected String color; private DefaultCar(String brand, String type) { this.brand = brand; this.type = type; } protected DefaultCar(Builder builder) { this(builder.getBrand(), builder.getType()); this.power = builder.power; this.torque = builder.torque; this.gears = builder.gears; this.color = builder.color; } @Override public String getBrand() { return brand; } @Override public String getType() { return type; } @Override public int getPower() { return power; } @Override public int getTorque() { return torque; } @Override public int getGears() { return gears; } @Override public String getColor() { return color; } } private static class ToyotaAvensisCarImpl implements Car { private final String color; public ToyotaAvensisCarImpl(String color) { this.color = color; } @Override public String getBrand() { return "Toyota"; } @Override public String getType() { return "Avensis"; } @Override public int getPower() { return 108; } @Override public int getTorque() { return 180; } @Override public int getGears() { return 6; } @Override public String getColor() { return color; } } private static Car TOYOTA_AVENSIS_WHITE = new Hidden.ToyotaAvensisCarImpl("White"); } public static class Builder extends Hidden.DefaultCar { public Builder(String brand, String type) { super(brand, type); } public Builder power(int power) { this.power = power; return this; } public Builder torque(int torque) { this.torque = torque; return this; } public Builder gears(int gears) { this.gears = gears; return this; } public Builder color(String color) { this.color = color; return this; } public Car build() { if ("toyota".equalsIgnoreCase(getBrand()) && "Avensis".equalsIgnoreCase(getType())) { if ("White".equals(getColor())) { return Hidden.TOYOTA_AVENSIS_WHITE; } else { return new Hidden.ToyotaAvensisCarImpl(getColor()); } } else { return new Hidden.DefaultCar(this); } } } }
Advantages
First, we define our Car interface with the getters that it shall provide and expose. All the Cars that we Build shall implement this interface but we are free to choose the implementation! This is a major advantage. We may, as shown above, create static pre-built Cars (like TOYOTA_AVENSIS_WHITE) that we can return instantly (remember that a Car is immutable and can be reused over and over again) whenever we deem possible. We can also create specially tailored implementations of Cars like the ToyotaAvensisCarImpl where we only hold a single bean property for the color. All the other getters always return the same value and thus, we do not need to hold member variables for these. Now, imagine that we create millions of Toyotas and further imagine how much memory we can save.Because all things are public in an interface, I have created an (inherently public) class named Hidden. In this inner class, I can decide the visibility of the things I put in here even though Hidden itself is visible from the outside. In the Hidden class I have defined a DefaultCar that implements the Car interface. This class can then be reused, both for the Builder and for any other implementation of the Car interface like the DefaultCar. This way, we do not have to declare variables several times (one for the Builder and one for each implementing class).
A small disadvantage is that the optional parameters are not final in the implementing class, so you need to take care, not to set the variables in any way. If you want them final, you can always refrain from extending DefaultCar at the cost of being forced to define them directly again in the implementing classes. The choice is yours.
I think the Interface Builder Pattern provides a number of important advantages over the normal Builder Pattern with concrete classes.
Happy coding!