Update: There’s a newer version of the code in this post; the newer version allows you to call the arguments in any order.
A co-worker of mine recently ran across this post by Eamonn McManus, “Using the builder pattern with subclasses,” and after due consideration, settled on what McManus calls “a shorter, smellier variant.”
Shorter because:
- it has only one Builder class per class
Smellier because:
- it uses raw types
- the builder’s
self()
method requires an unchecked cast
Frankly, I don’t find the shortness here a compelling argument for the smell, but I also think there’s more shortness to be found in McManus’ design while remaining fragrant. Thus:
class Shape { private final double opacity; public double getOpacity() { return opacity; } public static abstract class Builder<T extends Shape> { private double opacity; public Builder<T> opacity(double opacity) { this.opacity = opacity; return this; } public abstract T build(); } public static Builder<?> builder() { return new Builder<Shape>() { @Override public Shape build() { return new Shape(this); } }; } protected Shape(Builder<?> builder) { this.opacity = builder.opacity; } } class Rectangle extends Shape { private final double height; private final double width; public double getHeight() { return height; } public double getWidth() { return width; } public static abstract class Builder<T extends Rectangle> extends Shape.Builder<T> { private double height; private double width; public Builder<T> height(double height) { this.height = height; return this; } public Builder<T> width(double width) { this.width = width; return this; } } public static Builder<?> builder() { return new Builder<Rectangle>() { @Override public Rectangle build() { return new Rectangle(this); } }; } protected Rectangle(Builder<?> builder) { super(builder); this.height = builder.height; this.width = builder.width; } } class RotatedRectangle extends Rectangle { private final double theta; public double getTheta() { return theta; } public static abstract class Builder<T extends RotatedRectangle> extends Rectangle.Builder<T> { private double theta; public Builder<T> theta(double theta) { this.theta = theta; return this; } } public static Builder<?> builder() { return new Builder<RotatedRectangle>() { @Override public RotatedRectangle build() { return new RotatedRectangle(this); } }; } protected RotatedRectangle(Builder<?> builder) { super(builder); this.theta = builder.theta; } } class BuilderTest { public static void main(String[] args) { RotatedRectangle rotatedRectangle = RotatedRectangle.builder() .theta(Math.PI / 2) .width(640) .height(400) .opacity(0.5d) .build(); System.out.println(rotatedRectangle.getTheta()); System.out.println(rotatedRectangle.getWidth()); System.out.println(rotatedRectangle.getHeight()); System.out.println(rotatedRectangle.getOpacity()); } }
Now, some notes:
- Where McManus’ generics are of the self-referential,
Builder<T extends Builder<T>>
variety (cf.Enum
), in mine aBuilder<T>
buildsT
s. Personally I think this will give fewer developers headaches. - My
Builder
s don’t require aself()
method; they simply returnthis
. - Like McManus’ preferred design, this one requires two
Builder
s per class, one abstract and one concrete. However, the concrete one is an anonymous inner class, so it’s less obtrusive, and there’s only one “boilerplate” method per class. A little longer than the “smelly” version in that respect, but in my opinion livable.
I think the main of the original design was the ability to call the methods on the builder in any order. In your approach, I cannot not write
RotatedRectangle.builder().opacity(0.5d).theta(Math.PI / 2).width(640).height(400).build();
You can’t? You should be able to, since each shape subclass’s Builder subclasses the Builder of the shape class above it.
Of course you cant. Your implementation of opacity() method returns a Builder instance of Shape.Builder type.
See this later version (although I really need to get around to fixing the syntax highlighting CSS): https://chrononaut.org/2013/03/19/subclassing-with-blochs-builder-pattern-revised/
If that doesn’t work with the code above, it’s a bug. It should work (I’ve done it).
Hi David,
try this test.
The .width(111) cause syntax error
public void testBla() {
RotatedRectangle rotatedRectangle = RotatedRectangle.builder()
.theta(Math.PI / 2)
.width(640)
.height(400)
.height(400)
.opacity(0.5d)
.width(111)
.opacity(0.5d)
.width(222)
.height(400)
.width(640)
.width(640)
.build();
System.out.println(rotatedRectangle.getTheta());
System.out.println(rotatedRectangle.getWidth());
System.out.println(rotatedRectangle.getHeight());
System.out.println(rotatedRectangle.getOpacity());
}
Pingback: Show Your Work — Subclassing with Bloch’s Builder pattern, revised
You’re right — good catch. Revised version here: http://showyourwork.chrononaut.org/2013/03/19/subclassing-with-blochs-builder-pattern-revised/
Probably you should mention about the revised post in the original post also, so that even if people don’t read all the comments, they will know about it.
Good idea — done.
Reblogged this on Café avec Laurent and commented:
I love this. I also have a headache now…