Java Builder模式和“深层”对象层次结构

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

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

public class Outermost { private String title; private int channel; private List 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类。

但是,这不会让生成器模式试图解决的问题变得笨拙吗? 我正在考虑类似的东西 – 这将强制执行“由内而外”的方法 – 这意味着在将Innermost对象添加到Middle对象之前,必须完全构造和实例化它。 但是我们都知道在实践中(特别是在构建XML或JSON时),我们很少有“及时”的信息来实现这一点。

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

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

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

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

假设我们有

  public OuterBuilder { // some outer attributes here private ArrayList 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. } 

我们可以将相同的模式应用于更高级别的嵌套。

为了解决您的第一点,每个属性的变量:取决于我们如何设计构建器以及我们的数据来自何处。 如果我们说来自用户界面,那么无论如何我们每个属性都有一个变量,我们并没有变得更糟。 如果按照我上面的建议,我们会迭代一些数据结构,那么构建器可能会负责它的数据结构。 在我的示例中,我们传递了MiddleData实例。 一些额外的耦合,但它确实封装了细节。

为了解决你的第二点,我们不会像往常一样构建东西,而是有效地使用构建器作为数据的积累点。 最后我们称之为“Go and Build”方法,但此时我们应该拥有所有数据,以便整个层次结构构建。

它可以做到,但它可能不值得做。 明显的实施……

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

……很快就遇到了问题。 如果你尝试类似的东西

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

它不会编译,因为opacity()不知道它返回一个Rectangle.Builder ,只是一个Shape.Builder 。 因此,您必须按顺序调用属性,从大多数派生到最少派生:

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

如果你想绕过这个,你需要使属性方法通用,这样超类方法仍然会返回子类构建器。 AFAIK无法让这100%可靠,但有了一些自我指涉的generics,你可以得到接近:

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

注意@SuppressWarnings注释; 如果一个子类违反了FooBuilder总是扩展FooSuperclassBuilderFooSuperclassBuilder ,那么系统就会崩溃。

你可以看到代码有多丑。 在这一点上,也许最好放弃第2项 ,而是冥想第16项:赞成合成而不是inheritance 。

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