2011-07-18 7 views
14

"深い"オブジェクト階層でBuilderパターンを使用するベストプラクティスは何ですか?具体的には、Joshua Blochによって提案されたBuilderパターンをXMLバインディングコードに適用するというアイデアを検討しました(私はSimpleXMLを使用していますが、この質問はどのケースにも当てはまります)。私のオブジェクト階層は4レベル深く、複雑さは様々です。それで、いくつかのレベルで私のオブジェクトのプロパティはいくつかありますが、いくつかのレベルでは10までです。Javaビルダーのパターンと "深い"オブジェクト階層

このような仮定的な例を考えてみましょう簡潔)私はビルダーを使用してOutermostオブジェクトの作成を強制したい場合

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

、何がそれについて移動するための最良の方法だろうか?最も明白な答えは、上記の各クラスに対してinner static Builderクラスを持つことです。

しかし、Builderパターンが解決しようとしている問題ほど扱いにくいものにならないでしょうか?私はこのようなことを考えています。これはInnermostオブジェクトを完全に構築してインスタンス化してからMiddleオブジェクトに追加する必要があることを意味する「内側に向いた」アプローチを強制します。しかし、実際には(特にXMLやJSONを構築しているとき)、これを達成するための情報は「タイムリー」であることはほとんどありません。

可能性があります。すべてのレベルにわたって、それぞれのプロパティごとに変数を持つことになります。最後にオブジェクトを作成します。つまり、コード内に複数のレベルの浮動小数点が浮かび上がってしまい、混乱することになります。

これをどのようにエレガントに達成するためのアイデアですか?

+0

IMHOビルダーパターンを使用している場合は、これらすべてのプライベートフィールドを最終的にする必要があります。 –

+0

@AdamGentそれを指摘してくれてありがとう。彼らは本当に私のアプリで最終的です。どういうわけか、ここで質問を投稿している間にそれを逃した。 – curioustechizen

答えて

6

Builderパターンhereの説明は、私があなたが何を参照しているかと思います。それはウィキペディアhereで説明されているパターンとは少し異なります。私は前者を好みます。

私が読んだ説明に従って、カプセル化の構築やカプセル化の喪失が避けられないことが懸念されることはありません。私にとって大きな問題は、生データの構造です。

は、我々は我々がネストの更なるレベルまで同じパターンを適用することができ

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

を必要として今、私たちはできるだけ多くmiddleBuildersを作成することができます

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

があるとします。

最初の点に対処するために、すべてのプロパティの変数は、ビルダーの設計方法とデータの出所によって異なります。私たちがUIから来ているとすれば、とにかくプロパティごとに変数がかなりありますが、それほど悪くはありません。上記の私の提案によると、データ構造を反復しているのであれば、そのデータ構造を反復する責任が建築業者にあるかもしれません。私の例では、MiddleDataインスタンスを渡します。いくつかの余分なカップリングが、それは詳細をカプセル化します。

2つ目の問題に対処するために、私たちは物事を構築するのではなく、ビルダーをデータの蓄積ポイントとして効果的に使用しています。最終的には、 "Go and Build"メソッドと呼ばれますが、その時点ですべてのデータを配置して階層全体が構築されるようにする必要があります。

+0

それは詳細だったし、それは速かった!さて、あなたが指摘したようにBuilderの以前の記述を参照しています。しかし、私はあなたがここで何を示唆しているのか分かりません。クラス階層と並行するBuilderオブジェクトの階層があると言っていますか?たぶん、その答えは、私はあなたが "行くとビルド"がインサイドアウトする必要はないと思う理由を理解するのに役立ちます。 – curioustechizen

+1

はい、中部ビルダーと中型ビルダーがインナービルダーについて知っていることを知っているOuter Builderについて考えています。私はあなたがOutside-Inを構築することができない何らかの順序でデータを受け取るかもしれないことを心配していました。したがって、データの解釈のために私は、私たちが希望する順序でビルダーをビルドすることができ、おそらくビルダーにデータを蓄積できると主張しています。ある時点で、私たちは必要なものすべてを持っており、その時点でOuterにビルドを開始するように指示します。このビルドは、順番に再帰的に内部的に機能します。だから、最も内側のオブジェクトが最初に構築される可能性があります。 – djna

+0

はい - 私は今あなたの意見を見ます。私はこのアプローチを試し、ここで私の発見を更新します。 – curioustechizen

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()ので、それは​​、ちょうどShape.Builder<Rectangle>を返します知りません。だから、少なくとも由来に最も由来から、順番に属性を呼び出す必要があり:

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<Foo, FooBuilder>に拡張されるという規則が破られた場合、システムは故障します。

コードがどのように醜いかを見ることができます。この時点で、Item 2を放棄し、代わりにItem 16: Favor composition over inheritanceを瞑想したほうがよいかもしれません。

+3

あなたの答えは非常に有益でした - しかし、私はあなたが元の質問を誤解したと思います。私は_inheritance trees_にBuilderパターンを適用するつもりはありませんでした。むしろ、私が解決しようとしていた問題は、オブジェクトが他の非プリミティブフィールドで構成されている場合にBuilderパターンを適用することでした。質問の例はこれを示しています。 – curioustechizen

1

JAXBを使用してXMLスキーマからコードを生成する場合は、jaxb2-rich-contract-pluginの "Fluent-builder"プラグインが役に立ちます。ビルダーを連鎖させることができるディープ・ビルダー・パターンを生成し、「end()」メソッドを使用してネストされたオブジェクトの構築を完了し、その親のBuilderコンテキストに戻ります。 しかし、与えられたJavaクラスのためにこれを手作業で書くことはちょっと面倒なようです...

関連する問題