Java Builder pattern and a “deep” object hierarchy

后端 未结 3 499
一个人的身影
一个人的身影 2021-02-04 06:08

What is the best practice for using Builder pattern in \"deep\" object hierarchies? To elaborate, I explored the idea of applying the Builder pattern as proposed by Joshua Bloch

3条回答
  •  悲哀的现实
    2021-02-04 07:11

    It can be done, but it's arguably not worth doing. The obvious implementation...

    class Shape
    {
        private final double opacity;
    
        public double getOpacity()
        {
            return opacity;
        }
    
        public static abstract class Builder {
    
            private double opacity;
    
            public Builder opacity(double opacity) {
                this.opacity = opacity;
                return this;
            }
    
            public abstract T build();
        }
    
        public static Builder builder() {
            return new Builder()
                {
                    @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 extends Shape.Builder {
            private double height;
            private double width;
    
            public Builder height(double height) {
                this.height = height;
                return this;
            }
    
            public Builder width(double width) {
                this.width = width;
                return this;
            }
        }
    
        public static Builder builder() {
            return new Builder()
                {
                    @Override
                        public Rectangle build()
                    {
                        return new Rectangle(this);
                    }
                };
        }
    
        protected Rectangle(Builder builder) {
            super(builder);
            this.height = builder.height;
            this.width = builder.width;
        }
    }
    

    ...quickly runs into a problem. If you try something like

    Rectangle r = Rectangle.builder().opacity(0.5).height(50).width(100).build();
    

    it's not going to compile, because opacity() doesn't know it's returning a Rectangle.Builder, just a Shape.Builder. So you have to call the attributes in order, from most-derived to least-derived:

    Rectangle r = Rectangle.builder().height(50).width(100).opacity(0.5).build();
    

    If you want to get around this, you need to make the attribute methods generic, so that the superclass methods will still return the subclass builders. There's no way AFAIK to make this 100% reliable, but with some self-referential generics you can get close:

    class Shape
    {
        private final double opacity;
    
        public double getOpacity ()
        {
            return opacity;
        }
    
        public static abstract class ShapeBuilder>
        {
    
            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
        {
            @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> extends ShapeBuilder
        {
            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
        {
            @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> extends Rectangle.RectangleBuilder
        {
            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
        {
            @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() );
        }
    }
    

    Note the @SuppressWarnings annotations; if a subclass breaks the convention that FooBuilder always extends FooSuperclassBuilder, the system breaks down.

    And you can see how ugly the code gets. At this point maybe it's better to abandon Item 2 and instead meditate on Item 16: Favor composition over inheritance.

提交回复
热议问题