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

发布于 2024-11-25 03:14:11 字数 1059 浏览 0 评论 0原文

在“深层”对象层次结构中使用生成器模式的最佳实践是什么?为了详细说明,我探索了将 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 对象,最好的方法是什么?最明显的答案是为上述每个类提供内部静态构建器类。

但是,这不会让事情变得像构建器模式试图解决的问题一样难以处理吗?我正在考虑这样的事情 - 这将强制执行“由内而外”的方法 - 意味着 Innermost 对象必须完全构造并实例化,然后才能添加到 Middle代码>对象。但我们都知道,在实践中(尤其是在构建 XML 或 JSON 时),我们很少有“及时”信息来完成这一任务。

很有可能,我们最终会为所有级别的每个属性都设置变量;并在最后创建对象。或者,最终会在代码中出现多个级别的 Builder,从而增加混乱。

那么,关于如何优雅地实现这一点有什么想法吗?

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, to my XML binding code (I am using SimpleXML but this question would apply to any case). My object hierarchy is 4 levels deep, with various degree of complexity. By that, I mean, in some levels I have just a couple of properties for my objects, whereas at some other levels I have up to 10.

So consider this hypothetical example (I am leaving out the Simple XML annotations for brevity)

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 ..*/
}

If I wanted to enforce creation of the Outermost object using builders, what would be the best way to go about it? The most obvious answer is to have inner static Builder classes for each of the above classes.

But, wouldn't that make things as unwieldy as the very problem Builder pattern tries to solve? I am thinking about stuff like - this will enforce an "inside out" approach - meaning that the Innermost object will have to be fully constructed and instantiated before it can be added to the Middle object. But we all know that in practice (especially when one is building XML or JSON), we rarely have "timely" information to accomplish this.

Chances are, one will end up having variables for each and every property - across all levels; and create the objects in the very end. OR, one will end up having Builder for multiple levels floating around in the code, adding to the confusion.

So, any ideas on how to elegantly accomplish this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

清君侧 2024-12-02 03:14:11

构建器模式的描述这里是我猜猜你指的是什么;它与维基百科此处中描述的模式略有不同,我更喜欢前者。

从我读到的描述中,我不认为您对构造顺序或封装损失不可避免的担忧。对我来说,最大的问题是原始数据的结构。

假设我们

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

现在可以根据需要创建任意数量的 middleBuilder

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

我们可以将相同的模式应用于进一步的嵌套级别。

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

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

The description of the Builder Pattern here is I guess what you are referring to; it's a .little different than the pattern described in Wikipedia here, I prefer the former.

I don't see that your concerns about order of construction or loss of encapsulation inevitable follow from the descriptions I read. For me the big question is the structure of your raw data.

Suppose we have

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

Now we can create as many middleBuilders as we need

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

We can apply the same pattern to the further levels of nesting.

To address your first point, a variable for every property: depends on how we design the builders and where our data is coming from. If we're, say coming from a UI then we pretty much have a variable per property anyway, we're no worse off. If as per my suggestion above we're iterating some data structure, then maybe the builder takes responsibility for iterpreting that data structure. In my example we pass MiddleData instances down. Some extra coupling but it does encapsulate the details.

To address your second point we don't build things as we go, instead we're effectively using the builder as the accumulation point for the data. Eventually we call the "Go and Build" method, but at that point we should have all the data in place so the whole hierarchy just builds.

恋竹姑娘 2024-12-02 03:14:11

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

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 r = Rectangle.builder().height(50).width(100).opacity(0.5).build();

如果您想解决此问题,则需要使属性方法通用,以便超类方法仍将返回子类构建器。据我所知,不可能做到 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 的约定,系统就会崩溃。

你可以看到代码变得多么丑陋。此时,也许最好放弃 第 2 项 并相反,冥想 第 16 条:优先考虑组合而不是继承

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

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

Note the @SuppressWarnings annotations; if a subclass breaks the convention that FooBuilder always extends FooSuperclassBuilder<Foo, FooBuilder>, 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.

゛清羽墨安 2024-12-02 03:14:11

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

If you generate code from an XML schema with JAXB, the "fluent-builder" plugin from jaxb2-rich-contract-plugin will help you. It generates a deep builder pattern where you can chain builders together, and use an "end()" method to finish building a nested object and return to the builder context of its parent.
However, writing this by hand for a given Java class seems a bit tedious...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文