So last year I posted about using Joshua Bloch’s Builder pattern to create objects from a hierarchy of subclasses.
I realized later that while I did have something like this completely working at one point, I had it working in C#, which has deceptively different generic semantics. (It also has named parameters, which makes the Builder pattern a fair bit less necessary.) As Zdenek Henek demonstrated (several weeks ago, sorry Zdenek!), in Java, the original version I posted doesn’t allow you to call the arguments in any order.
This one does:
class Shape { private final double opacity; public double getOpacity () { return opacity; } public static abstract class ShapeBuilder<S extends Shape, B extends ShapeBuilder<S, B>> { private double opacity; @SuppressWarnings( "unchecked" ) public B opacity ( double opacity ) { this.opacity = opacity; return (B) this; } public abstract S build (); } private static class DefaultShapeBuilder extends ShapeBuilder<Shape, DefaultShapeBuilder> { @Override public Shape build () { return new Shape( this ); } } public static ShapeBuilder<?, ?> builder () { return new DefaultShapeBuilder(); } protected Shape ( ShapeBuilder<?, ?> 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 RectangleBuilder<S extends Rectangle, B extends RectangleBuilder<S, B>> extends ShapeBuilder<S, B> { private double height; private double width; @SuppressWarnings( "unchecked" ) public B height ( double height ) { this.height = height; return (B) this; } @SuppressWarnings( "unchecked" ) public B width ( double width ) { this.width = width; return (B) this; } } public static RectangleBuilder<?, ?> builder () { return new DefaultRectangleBuilder(); } protected Rectangle ( RectangleBuilder<?, ?> builder ) { super( builder ); this.height = builder.height; this.width = builder.width; } private static class DefaultRectangleBuilder extends RectangleBuilder<Rectangle, DefaultRectangleBuilder> { @Override public Rectangle build () { return new Rectangle( this ); } } } class RotatedRectangle extends Rectangle { private final double theta; public double getTheta () { return theta; } public static abstract class RotatedRectangleBuilder<S extends RotatedRectangle, B extends RotatedRectangleBuilder<S, B>> extends Rectangle.RectangleBuilder<S, B> { private double theta; @SuppressWarnings( "Unchecked" ) public B theta ( double theta ) { this.theta = theta; return (B) this; } } public static RotatedRectangleBuilder<?, ?> builder () { return new DefaultRotatedRectangleBuilder(); } protected RotatedRectangle ( RotatedRectangleBuilder<?, ?> builder ) { super( builder ); this.theta = builder.theta; } private static class DefaultRotatedRectangleBuilder extends RotatedRectangleBuilder<RotatedRectangle, DefaultRotatedRectangleBuilder> { @Override public RotatedRectangle build () { return new RotatedRectangle( this ); } } } class BuilderTest { public static void main ( String[] args ) { 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() ); } }
Note though it requires some unchecked casts, and depends on the convention that each Builder’s generics are self-referential, which could present some risk if it’s extended further. Test early, test often.
I’m reading this code over and over again and while I managed to understand this and apply to my code I still fail to understand the part with double wildcard static method. What is the meaning of those wildcards? Why java allows to return different type of result in overridden method (ShapeBuilder and RectangleBuilder) when overriding with non-generic result is prohibited?
It’s been a long while since I looked at this code, but I think the fundamental problem is that there’s nothing more specific than those wildcards that would be valid. You could say
? extends Shape
,? extends Rectangle
etc., but that’s already implied by theShapeBuilder
andRectangleBuilder
type declarations, so it’s not necessary.To understand why you can’t get rid of the wildcards entirely, I think it’s helpful to think about collections. If you have a method that returns a
List
ofShape
, you can’t override it to return aList
ofRectangle
, because saying it’s aList
ofShape
implies that you should be able to put aCircle
in it, whereas saying it’s aList
ofRectangle
implies that you should only getRectangles
out of it.As it happens, there’s no similar danger here, but Java’s generics aren’t sophisticated enough to let you express that. (You probably could in C#.)
Stepping back a bit, I would say that the wildcards and unchecked casts here are a code smell and I’d think hard about putting something this complicated in production.
The Builder is not still a free flow. if I have to pass the builder instance to a method, the flow discontinues. public void build(RotatedRectangle.RotatedRectangleBuilder builder) {
builder.opacity(2).name(“”);
}
I’m not sure just what you’re trying to do (there’s no name() method?) but it should work as long as you pass all the generics.
public <S extends RotatedRectangle, B extends RotatedRectangle.RotatedRectangleBuilder<S>> void build(RotatedRectangle.RotatedRectangleBuilder<S> builder) {
builder.opacity(2).theta(12);
}
(Understand, I wouldn’t recommend designing a public API this way, but it might be useful in some corner cases.)