Se puede hacer, pero podría decirse que no vale la pena hacerlo. La implementación obvia ...
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;
}
}
... rápidamente se topa con un problema. Si intenta algo así como
Rectangle r = Rectangle.builder().opacity(0.5).height(50).width(100).build();
que no va a compilar, porque opacity()
no sabe que está devolviendo un Rectangle.Builder
, sólo un Shape.Builder<Rectangle>
. Así que hay que llamar a los atributos en orden, de más derivada al menos derivados:
Rectangle r = Rectangle.builder().height(50).width(100).opacity(0.5).build();
Si desea evitar esto, es necesario hacer que los métodos de atributos genéricos, por lo que los métodos de superclase seguirá siendo devolver los constructores de la subclase. No hay manera de que yo sepa para que este 100% fiable, pero con algunos medicamentos genéricos autorreferenciales que pueden acercarse:
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) // note attribs can be set in any order
.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());
}
}
Nota @SuppressWarnings
las anotaciones; si una subclase infringe la convención de que FooBuilder
siempre se extiende FooSuperclassBuilder<Foo, FooBuilder>
, el sistema se descompone.
Y puede ver lo feo que es el código. En este punto, tal vez es mejor abandonar Item 2 y en su lugar meditar en Item 16: Favor composition over inheritance.
IMHO Si está utilizando el patrón de generador, debe hacer que todos los campos privados sean definitivos. –
@ AdamGent Gracias por señalarlo. De hecho, son definitivos en mi aplicación. De alguna manera se perdió eso al publicar la pregunta aquí. – curioustechizen