【问题标题】:Java Builder pattern and a "deep" object hierarchyJava Builder 模式和“深层”对象层次结构
【发布时间】:2011-07-18 14:34:19
【问题描述】:

在“深层”对象层次结构中使用构建器模式的最佳做法是什么?为了详细说明,我探索了将 Joshua Bloch 提出的 Builder 模式应用于我的 XML 绑定代码的想法(我使用的是 SimpleXML,但这个问题适用于任何情况)。我的对象层次结构有 4 级深,具有不同程度的复杂性。我的意思是,在某些关卡中,我的对象只有几个属性,而在其他某些关卡中,我最多有 10 个。

所以考虑这个假设的例子(为简洁起见,我省略了简单的 XML 注释)

public class Outermost {

    private String title;
    private int channel;
    private List<Middle> middleList;

}

class Middle{
    private int id;
    private String name;
    private boolean senior;
    /* ... ... 10 such properties */

    private Innermost inner;
}

class Innermost{
    private String something;
    private int foo;
    /* ... Few more of these ..*/
}

如果我想强制使用构建器创建 Outermost 对象,最好的方法是什么?最明显的答案是为上述每个类设置inner static Builder 类。

但是,这不会像 Builder 模式试图解决的问题那样使事情变得笨拙吗?我正在考虑类似的东西 - 这将强制执行“由内而外”的方法 - 这意味着必须完全构造和实例化 Innermost 对象,然后才能将其添加到 Middle 对象中。但我们都知道,在实践中(尤其是在构建 XML 或 JSON 时),我们很少有“及时”的信息来完成这项工作。

很有可能,每个属性最终都会有变量 - 跨越所有级别;并最终创建对象。或者,最终会在代码中浮动多个级别的 Builder,从而增加混乱。

那么,关于如何优雅地完成此任务的任何想法?

【问题讨论】:

  • 恕我直言,如果您使用的是构建器模式,您应该将所有这些私有字段设为 final。
  • @AdamGent 感谢您指出这一点。它们在我的应用程序中确实是最终的。在此处发布问题时不知何故错过了这一点。

标签: java design-patterns builder


【解决方案1】:

Builder模式here的描述我猜你指的是什么;它与维基百科here 中描述的模式略有不同,我更喜欢前者。

我没有看到您对构建顺序或封装丢失的担忧不可避免地来自我阅读的描述。对我来说,最大的问题是原始数据的结构。

假设我们有

 public OuterBuilder {
     // some outer attributes here

     private ArrayList<MiddleBuilder> m_middleList;

     public OuterBuild( mandatory params for Outers ){
          // populate some outer attributes
          // create empty middle array
     }

     public addMiddle(MiddleBuilder middler) {
              m_middleList.add(middler);
     } 
 }

现在我们可以根据需要创建任意数量的中间构建器

 while (middleDataIter.hasNext() ) {
      MiddleData data = middleDateIter.next();
      // make a middle builder, add it.
 }

我们可以将相同的模式应用到更深层次的嵌套。

为了解决您的第一点,每个属性的变量:取决于我们如何设计构建器以及我们的数据来自哪里。如果我们是,比如说来自 UI,那么我们几乎每个属性都有一个变量,我们的情况也不会更糟。如果按照我上面的建议,我们正在迭代一些数据结构,那么也许构建器负责迭代该数据结构。在我的示例中,我们向下传递 MiddleData 实例。一些额外的耦合,但它确实封装了细节。

为了解决您的第二点问题,我们不会边做边构建东西,而是有效地将构建器用作数据的累积点。最终我们调用了“Go and Build”方法,但此时我们应该拥有所有数据,以便构建整个层次结构。

【讨论】:

  • 这很详细,也很快!好吧,正如您所指出的,我确实参考了之前对 Builder 的描述。但我不确定我是否理解你在这里的建议。你是说我有一个与我的类层次结构平行的 Builder 对象层次结构吗?也许这个答案会帮助我理解为什么你认为“Go and Build”不需要由内而外。
  • 是的,我认为外部构建者知道中间构建者,中间构建者知道内部构建者。我认为您担心我们可能会以某种顺序接收数据,这不允许我们从外向内构建。因此,对于数据解释,我声称我们可以按照我们喜欢的任何顺序构建构建器,很可能是在外部,并按照我们的意愿在构建器中积累数据。在某个时刻,我们拥有了我们需要的一切,然后告诉 Outer 开始构建,然后递归地向内工作。所以最里面的对象很可能首先被构建。
  • 是的 - 我现在明白你的意思了。我将尝试这种方法并在此处更新我的发现。
【解决方案2】:

可以做到,但可以说不值得。显而易见的实现...

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;
    }
}

...很快就会遇到问题。如果你尝试类似

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

它不会编译,因为opacity() 不知道它返回一个Rectangle.Builder,只是一个Shape.Builder&lt;Rectangle&gt;。所以你必须按顺序调用属性,从最多派生到最少派生:

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

如果你想解决这个问题,你需要使属性方法通用,这样超类方法仍然会返回子类构建器。 AFAIK 无法让这个 100% 可靠,但是通过一些自引用泛型你可以接近:

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() );
    }
}

注意@SuppressWarnings 注释;如果一个子类违反了FooBuilder 总是扩展FooSuperclassBuilder&lt;Foo, FooBuilder&gt; 的约定,系统就会崩溃。

您可以看到代码变得多么丑陋。在这一点上,也许最好放弃Item 2,转而冥想Item 16: Favor composition over inheritance

【讨论】:

  • 您的回答提供了很多信息 - 但是,我认为您误解了最初的问题。我不打算将 Builder 模式应用于继承树。相反,我试图解决的问题是在对象由其他非原始字段组成时应用 Builder 模式。问题中的示例说明了这一点。
【解决方案3】:

如果您使用 JAXB 从 XML 模式生成代码,jaxb2-rich-contract-plugin 的“fluent-builder”插件将为您提供帮助。它生成一个深度构建器模式,您可以在其中将构建器链接在一起,并使用“end()”方法完成嵌套对象的构建并返回其父对象的构建器上下文。 但是,为给定的 Java 类手动编写此代码似乎有点乏味...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多