Tuesday, 15 December 2009

Builder Pattern Abused

Everyone has read Joshua Bloch's builder pattern, which he has also described at his "Effective Java Reloaded" sessions at Javaone. However in this post, I am going to describe a misuse of this pattern, when used in conjunction with inheritance. 
When building a relational model, the biggest challenge is to maintain the integrity of inheritance and reduce the visibility of the properties completely. However if you need builders for this model, then you either have to create a class hierarchy of builders similar to the model itself or make all accesses protected (ugly #1) so that builders could appropriately initialize them. Making the properties less visible has at least two disadvantages though (1) you cannot make them final and ensure construction safe and (2) you'll end up copying the builders to more than one class (ugly #2) I recently came across a horrendous implementation of the hierarchy of builders where the author could not solve the above issues elegantly. So he had resorted to creating copy constructors (ugly #3) for the builders and also duplicating the methods for each of the builders. The following code snippet exactly reproduces the code, in an example class hierarchy and its builders.
abstract class Shape {
    private String name;
    private Dimension dimension;

    public enum Dimension {
        TWO_D, THREE_D;
    }

    public static abstract class ShapeBuilder {
        private String name;
        private Dimension dimension;

        protected ShapeBuilder copyOf(ShapeBuilder builder) {
            ShapeBuilder copy = createBuilder();
            copy.name = builder.name;
            copy.dimension = builder.dimension;
            return copy;
        }
        public ShapeBuilder name(String name) {
            ShapeBuilder bldr = copyOf(this);
            bldr.name = name;
            return bldr;
        }
        public ShapeBuilder dimension(Dimension dimension) {
            ShapeBuilder bldr = copyOf(this);
            bldr.dimension = dimension;
            return bldr;
        }
        public Shape build() {
            Shape s = createInstance();
            s.name = this.name;
            s.dimension = this.dimension;
            return s;
        }
        abstract Shape createInstance();
        abstract ShapeBuilder createBuilder();
    }
    // omitting getters for clarity
}
abstract class TwoDShape extends Shape {
    private double area;

    public static abstract class TwoDShapeBuilder extends ShapeBuilder {
        private double area;

        protected TwoDShapeBuilder copyOf(TwoDShapeBuilder builder) {
            TwoDShapeBuilder copy = (TwoDShapeBuilder) super.copyOf(builder);
            copy.area = this.area;
            return copy;
        }
        public TwoDShapeBuilder name(String name) {
            return (TwoDShapeBuilder) super.name(name);
        }
        public TwoDShapeBuilder dimension(Dimension dimension) {
            return (TwoDShapeBuilder) super.dimension(dimension);
        }
        public TwoDShapeBuilder area(double area) {
            TwoDShapeBuilder bldr = copyOf(this);
            bldr.area = area;
            return bldr;
        }
        public TwoDShape build() {
            TwoDShape s = (TwoDShape) super.build();
            s.area = this.area;
            return s;
        }
    }
}
class Circle extends TwoDShape {
    private double radius;

    public static class CircleBuilder extends TwoDShapeBuilder {
        private double radius;

        protected CircleBuilder copyOf(CircleBuilder builder) {
            CircleBuilder copy = (CircleBuilder) super.copyOf(builder);
            copy.radius = this.radius;
            return copy;
        }
        public CircleBuilder name(String name) {
            return (CircleBuilder) super.name(name);
        }
        public CircleBuilder dimension(Dimension dimension) {
            return (CircleBuilder) super.dimension(dimension);
        }
        public CircleBuilder area(double area) {
            return (CircleBuilder) super.area(area);
        }
        public CircleBuilder radius(double radius) {
            CircleBuilder bldr = copyOf(this);
            bldr.radius = radius;
            return bldr;
        }
        public Circle build() {
            Circle s = (Circle) super.build();
            s.radius = this.radius;
            return s;
        }
        @Override
        protected Shape createInstance() {
            return new Circle();
        }
        @Override
        protected ShapeBuilder createBuilder() {
            return new CircleBuilder();
        }
    }
}
abstract class ThreeDShape extends Shape {
    private double volume;
    // builder similar to 2DShape
}
class Cube extends ThreeDShape {
    private int side;
    // builder similar to Circle
}
Now extrapolate this class into an enterprise relational model consisting of 10-15 objects with nested hierarchies and you can imagine the amount of stupid code that's been written. If you haven't solved this problem yourself before, please stop here and think for a solution. The biggest issue here is, when you create a CircleBuilder and call the name() method, you are again expecting a CircleBuilder back and not a ShapeBuilder. Thankfully there is a generic implementation (partly under documented) which helps to solve problems of this nature. The modified code is given below
abstract class Shape {
    private String name;
    private Dimension dimension;

    public enum Dimension {
        TWO_D, THREE_D;
    }

    public static abstract class ShapeBuilder<S extends Shape, SB extends ShapeBuilder<S, SB>> {
        private String name;
        private Dimension dimension;

        @SuppressWarnings("unchecked")
        public SB name(String name) {
            this.name = name;
            return (SB) this;
        }
        @SuppressWarnings("unchecked")
        public SB dimension(Dimension dimension) {
            this.dimension = dimension;
            return (SB) this;
        }
        public T build() {
            T result = createInstance();
            result.name = this.name;
            result.dimension = this.dimension;
            return result;
        }
        abstract T createInstance();
    }
}
abstract class TwoDShape extends Shape {
    private double area;

    public static abstract class TwoDShapeBuilder<S extends TwoDShape, SB extends TwoDShapeBuilder<S, SB>>
            extends ShapeBuilder<T, SB> {
        private double area;

        @SuppressWarnings("unchecked")
        public SB area(double area) {
            this.area = area;
            return (SB) this;
        }
        public T build() {
            T result = (T) super.build();
            result.area = this.area;
            return result;
        }
    }
}
class Circle extends TwoDShape {
    private double radius;

    public static class CircleBuilder extends
            TwoDShapeBuilder<Circle, CircleBuilder> {
        private double radius;

        public CircleBuilder radius(double radius) {
            this.radius = radius;
            return this;
        }
        public Circle build() {
            Circle s = (Circle) super.build();
            s.radius = this.radius;
            return s;
        }
        @Override
        protected Circle createInstance() {
            return new Circle();
        }
    }
}
The only thing ugly about this code is that the builders themselves have to be cast to the generic implementation because of the type erasure. However this would be solved by the new "Type Inference" language feature that will be introduced in Java 7.
 

1 comment:

  1. Thank you for a very nice and informative post. We use builders a lot at work and your article about builders and inheritance was a "lifesaver". You can find a slightly modified implementation of your version here: http://is.gd/Eq5rwq, and test case here: http://is.gd/eJI2lE

    Regards
    Leif Olsen

    ReplyDelete