2016-06-13 8 views
1

(Javaで)Builderパターンは、通常、次のように例で示されている:クラスメンバーがビルダーパターンで複製されているのはなぜですか?

public class MyClass { 

    private String member1; 

    private String member2; 

    // Getters & setters 

    public class MyClassBuilder { 

     private String nestedMember1; 

     private String nestedMember2; 

     public MyClassBuilder withMember1(String member1) { 
      this.nestedMember1 = member1; 
      return this; 
     } 

     public MyClassBuilder withMember2(String member2) { 
      this.nestedMember1 = member1; 
      return this; 
     } 

     public MyClass build() { 
      MyClass myClass = new MyClass(); 

      myClass.member1 = nestedMember1; 
      myClass.member2 = nestedMember2; 

      return myClass; 
     } 

    } 

} 

だから我々は2つの非常に類似したクラスを取得し、私たちはメンバーの大多数を持っている場合、このパターンがほとんど適用されているので、我々が得ます類似点の大きな量。これは私の意見ではドライ原則に違反しています。

次のコードに問題がありますか?あなたがお勧めのコードで

public class MyClass { 

    private String member1; 

    private String member2; 

    // Getters & setters 

    public class MyClassBuilder { 

     private MyClass myClass = new MyClass(); 

     public MyClassBuilder withMember1(String member1) { 
      myClass.member1 = member1; 
      return this; 
     } 

     public MyClassBuilder withMember2(String member2) { 
      myClass.member2 = member1; 
      return this; 
     } 

     public MyClass build() { 
      return myClass; 
     } 

    } 

} 

答えて

2

だから我々は[...] 2つの非常に類似したクラスを取得します。これは私の意見ではドライ原則に違反しています。

これは議論の余地があります。結局のところ、ビルダークラスのフィールドは、製品クラスのフィールドとはまったく異なる場合があります。たとえ似ていても、多くの場合、製品のフィールドのサブセットのみがビルダーに表示される必要があります。

DRYの定義(「すべての知識は、システム内で単一で明白で信頼できる表現でなければなりません」)が与えられた場合、共通実装がDRYに違反しないという議論ができます。ビルダーのフィールドは名前が一致しても、実際には2つの異なる知識を表します。前者は既存のオブジェクトの内部状態に関する情報を表し、後者は製品の別のインスタンスを作成するために必要なデータを表します。

とにかく、より実用的な部分に移動しましょう。

次のコードに問題がありますか?

は、一般的に言えば、それは同じビルダーインスタンス上build()への後続の各呼び出しはMyClassの新しいインスタンスを作成したり、少なくとも、例外をスローすることを期待するのは、あなたのMyClassBuilderを使用して、プログラマのための合理的です、ビルダーインスタンスの再利用が実装でサポートされていない場合

代わりに、build()への呼び出しごとに、事前に作成された1つの同じプリコンパイル済みのMyClassインスタンスが返されます。多くの場合、この動作は、MyClassBuilderのユーザーには少し不愉快な驚きを示すかもしれません。次の例を考えてみましょう:

// Example 1 
MyClass.MyClassBuilder builder = new MyClass.MyClassBuilder() 
    .withMember1("foo") 
    .withMember2("bar"); 

MyClass product = builder.build(); 

builder.withMember2("baz"); 

// The following line prints out "baz", instead of "bar"... 
// But wait, I haven't even touched product! 
System.out.println(product.getMember2()); 


// Example 2 
MyClass.MyClassBuilder builder = new MyClass.MyClassBuilder() 
    .withMember1("hello") 
    .withMember2("world"); 

MyClass product1 = builder.build(); 
MyClass product2 = builder.build(); 

product1.setMember1("bye"); 

// The following line prints out "bye", instead of "hello"... 
// Wait, what??! 
System.out.println(product2.getMember1()); 

次に仮想的なプログラマはすべての製品が実際に1と同じオブジェクト(ビルダーが親切に言及彼らがbuild()呼ばれるたびに返却されたに)していることを理解するであろう。そして、彼らは本当に非常に怒っていました。

MyClassがクローン作成をサポートしているか、コピーコンストラクタを持っている場合は、上記の期待値を実際に満たすことができます。この場合、build()MyClassBuilderの内部MyClassインスタンスをコピーし、すべての呼び出しで新しいコピーを返すことができます。簡単のため 、のプライベートコピーコンストラクタを追加してみましょう。この時点で

public class MyClass { 

    private String member1; 

    private String member2; 

    public String getMember1() { 
     return member1; 
    } 

    public String getMember2() { 
     return member2; 
    } 

    // Getters & setters 

    private MyClass() { 

    } 

    /** 
    * Copy constructor to be used by builder. 
    * @param other Original MyClass instance. 
    */ 
    private MyClass(MyClass other) { 
     // Strings are immutable, so we can simply copy references. 
     // In general, you should consider whether a deep copy needs to be performed for a field. 
     this.member1 = other.member1; 
     this.member2 = other.member2; 
    } 

    public static class MyClassBuilder { 

     private MyClass myClass = new MyClass(); 

     public MyClassBuilder withMember1(String member1) { 
      myClass.member1 = member1; 
      return this; 
     } 

     public MyClassBuilder withMember2(String member2) { 
      myClass.member2 = member1; 
      return this; 
     } 

     public MyClass build() { 
      return new MyClass(myClass); // The client will now get a copy 
     } 
    } 
} 

一つは、私たちのビルダーの実装は、より多くの栄光Prototypeのように見えるし始めていることに気づくかもしれません。実際に、プライベートコピーコンストラクタをpublic clone()メソッドに置き換える場合、MyClassは文字通りPrototypeの例になります。その時点で、ほとんどの場合、MyClassBuilderはまったく必要ありません。それでも、ビルダーのそのような実装は正常に動作します。


あなたのアプローチを持つ別の、間違いなく、より多くのマイナー、問題がある:それは、値がfinal宣言されてから、プロセスを構築時に設定されているMyClass内のフィールドを、防ぐことができます。あなたが単に上記のフィールドにパブリックセッターを提供しないことを選ぶことができるので、これはクライアントコードにとってあまり重要ではありません。しかし、MyClassで明示的に宣言されたフィールドを持つことは、MyClassのコードで作業しているプログラマーが誤って値を変更することを防ぎ、バグを見つけにくくなるため、フィールドを持つことはまだ素晴らしいです。今

、「普通」の実装、あなたはそれを提示するよう、また、この問題に苦しんでいるが、そこに次のようにそれがMyClassに別のコンストラクタを導入し、ビルダーのbuild()メソッドを書き換えることによって対処することができます

public MyClass build() { 
     return new MyClass(nestedMember1, nestedMember2); 
    } 

一般にJavaでBuilderを実装する場合、内側のBuilderクラスをstaticと宣言する必要があるので、外部コードまたは便利な静的メソッド(getBuilder())をMyClassにすると、既存のインスタンスMyClass。これはどちらの実装でも当てはまりますので、staticの不足はあなたのタイプミスです) もう一つの小さな点:MyClassにプライベートコンストラクタを指定することをお勧めします。クライアントがビルダークラスを使用することを望むのであればそれらが直接MyClassのインスタンスを構築しないようにしてください。

3

、あなたがちょうど使用して、新しいオブジェクトにあなたがビルドメソッドを呼び出すたびに作成されていないので、あなたは常に同じオブジェクトを使用している同じビルダーのインスタンスで複数のオブジェクトを構築しようとした場合いつも同じオブジェクト。実際、あなたのビルド方法では何も構築していません。 Builderパターンは、毎回新しいオブジェクトを構築するために使用されるため、そのように実装されています。

同様の質問がここに答えた:

What makes up a Builder Pattern?

関連する問題