在“深层”对象层次结构中使用Builder模式的最佳做法是什么?为了详细说明,我探讨了将Joshua Bloch提出的Builder模式应用于我的XML绑定代码的想法(我使用的是SimpleXML,但这个问题适用于任何情况)。我的对象层次结构深度为4级,具有不同程度的复杂性。通过这种方式,我的意思是,在某些级别中,我只有几个属性用于我的对象,而在其他一些级别,我有多达10个属性。
所以请考虑这个假设的例子(为简洁起见,我省略了Simple 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
个类。
但是,这不会让生成器模式试图解决的问题变得笨拙吗?我正在考虑这样的事情 - 这将强制执行“由内而外”的方法 - 这意味着Innermost
对象必须完全构造并实例化,然后才能将其添加到Middle
对象中。但我们都知道在实践中(特别是在构建XML或JSON时),我们很少有“及时”的信息来实现这一点。
很可能,人们最终会在每个级别都拥有每个属性的变量;并在最后创建对象。或者,最终会让代码中出现多个级别的Builder,这会增加混乱。
那么,关于如何优雅地完成这个的任何想法?
答案 0 :(得分:6)
构建器模式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);
}
}
现在我们可以根据需要创建尽可能多的midBuilders
while (middleDataIter.hasNext() ) {
MiddleData data = middleDateIter.next();
// make a middle builder, add it.
}
我们可以将相同的模式应用于更高级别的嵌套。
要解决您的第一点,每个属性的变量:取决于我们如何设计构建器以及我们的数据来自何处。如果我们说来自用户界面,那么我们几乎每个属性都有一个变量,我们并没有变得更糟。如果按照我上面的建议,我们会迭代一些数据结构,那么构建器可能会负责它的数据结构。在我的示例中,我们传递了MiddleData实例。一些额外的耦合,但它确实封装了细节。
为了解决你的第二点,我们不会像往常一样构建东西,而是有效地使用构建器作为数据的积累点。最后我们称之为“Go and Build”方法,但此时我们应该准备好所有数据,这样整个层次结构才能构建。
答案 1 :(得分:4)
可以做到,但可以说它不值得做。显而易见的实施......
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<Rectangle>
。因此,您必须按顺序调用属性,从大多数派生到最少派生:
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<Foo, FooBuilder>
的约定,则系统会崩溃。
你可以看到代码有多丑。在这一点上,最好放弃Item 2而是冥想Item 16: Favor composition over inheritance。
答案 2 :(得分:1)
如果使用JAXB从XML模式生成代码,那么&#34; fluent-builder&#34;来自jaxb2-rich-contract-plugin的插件会对您有所帮助。它会生成一个深层构建器模式,您可以将构建器链接在一起,并使用&#34; end()&#34;完成构建嵌套对象并返回其父级的构建器上下文的方法。 但是,为给定的Java类手动编写这个看起来有点单调乏味......