Minborg

Minborg
Minborg

Sunday, September 14, 2014

The Interface Builder Pattern, a Follow-up Post on the Builder Pattern

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:
  1. The Car.Builder can select between many different implementations of Cars depending on what  is appropriate at build() time.
  2. 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
  3. We can have cached Cars that we can provide instantly at build() time.
This adds a lot of flexibility to your Builder as can be seen in this example:

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!