152

この Dr Dobbs の記事、特に Builder パターンについて説明すると、Builder をサブクラス化する場合はどのように処理すればよいでしょうか? サブクラス化して GMO ラベルを追加したい例のカットダウン バージョンを取り上げると、単純な実装は次のようになります。

public class NutritionFacts {                                                                                                    

    private final int calories;                                                                                                  

    public static class Builder {                                                                                                
        private int calories = 0;                                                                                                

        public Builder() {}                                                                                                      

        public Builder calories(int val) { calories = val; return this; }                                                                                                                        

        public NutritionFacts build() { return new NutritionFacts(this); }                                                       
    }                                                                                                                            

    protected NutritionFacts(Builder builder) {                                                                                  
        calories = builder.calories;                                                                                             
    }                                                                                                                            
}

サブクラス:

public class GMOFacts extends NutritionFacts {                                                                                   

    private final boolean hasGMO;                                                                                                

    public static class Builder extends NutritionFacts.Builder {                                                                 

        private boolean hasGMO = false;                                                                                          

        public Builder() {}                                                                                                      

        public Builder GMO(boolean val) { hasGMO = val; return this; }                                                           

        public GMOFacts build() { return new GMOFacts(this); }                                                                   
    }                                                                                                                            

    protected GMOFacts(Builder builder) {                                                                                        
        super(builder);                                                                                                          
        hasGMO = builder.hasGMO;                                                                                                 
    }                                                                                                                            
}

これで、次のようなコードを記述できます。

GMOFacts.Builder b = new GMOFacts.Builder();
b.GMO(true).calories(100);

しかし、順序を間違えると、すべて失敗します。

GMOFacts.Builder b = new GMOFacts.Builder();
b.calories(100).GMO(true);

もちろん、問題はではなくをNutritionFacts.Builder返すことです。この問題をどのように解決するか、または使用するより良いパターンがありますか?NutritionFacts.BuilderGMOFacts.Builder

注:同様の質問に対するこの回答は、上記のクラスを提供します。私の質問は、ビルダー呼び出しが正しい順序であることを確認する問題に関するものです。

4

10 に答える 10

48

記録のためだけに、

unchecked or unsafe operations警告

return (T) this;@dimadima と @Thomas N. が話しているステートメントについては、次の解決策が特定のケースに適用されます。

ジェネリック型 (この場合) をabstract宣言するビルダーを作成し、次のように抽象メソッドを宣言します。T extends Builderprotected abstract T getThis()

public abstract static class Builder<T extends Builder<T>> {

    private int calories = 0;

    public Builder() {}

    /** The solution for the unchecked cast warning. */
    public abstract T getThis();

    public T calories(int val) {
        calories = val;

        // no cast needed
        return getThis();
    }

    public NutritionFacts build() { return new NutritionFacts(this); }
}

詳細については、 http: //www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205 を参照してください。

于 2016-01-12T10:59:03.463 に答える
7

メソッドもオーバーライドcalories()して、拡張ビルダーを返すことができます。これは、Java が共変の戻り値の型をサポートしているため、コンパイルされます。

public class GMOFacts extends NutritionFacts {
    private final boolean hasGMO;
    public static class Builder extends NutritionFacts.Builder {
        private boolean hasGMO = false;
        public Builder() {
        }
        public Builder GMO(boolean val)
        { hasGMO = val; return this; }
        public Builder calories(int val)
        { super.calories(val); return this; }
        public GMOFacts build() {
            return new GMOFacts(this);
        }
    }
    [...]
}
于 2013-06-18T09:07:08.940 に答える
2

山かっこや 3 つに目を向けたくない場合、またはおそらくあなたを感じたくない場合...うーん...つまり......あなたのチームの他のメンバーはすぐに好奇心を持って理解します繰り返し発生するジェネリック パターンの場合、これを行うことができます。

public class TestInheritanceBuilder {
  public static void main(String[] args) {
    SubType.Builder builder = new SubType.Builder();
    builder.withFoo("FOO").withBar("BAR").withBaz("BAZ");
    SubType st = builder.build();
    System.out.println(st.toString());
    builder.withFoo("BOOM!").withBar("not getting here").withBaz("or here");
  }
}

による支援

public class SubType extends ParentType {
  String baz;
  protected SubType() {}

  public static class Builder extends ParentType.Builder {
    private SubType object = new SubType();

    public Builder withBaz(String baz) {
      getObject().baz = baz;
      return this;
    }

    public Builder withBar(String bar) {
      super.withBar(bar);
      return this;
    }

    public Builder withFoo(String foo) {
      super.withFoo(foo);
      return this;
    }

    public SubType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      SubType tmp = getObject();
      setObject(new SubType());
      return tmp;
    }

    protected SubType getObject() {
      return object;
    }

    private void setObject(SubType object) {
      this.object = object;
    }
  }

  public String toString() {
    return "SubType2{" +
        "baz='" + baz + '\'' +
        "} " + super.toString();
  }
}

および親タイプ:

public class ParentType {
  String foo;
  String bar;

  protected ParentType() {}

  public static class Builder {
    private ParentType object = new ParentType();

    public ParentType object() {
      return getObject();
    }

    public Builder withFoo(String foo) {
      if (!"foo".equalsIgnoreCase(foo)) throw new IllegalArgumentException();
      getObject().foo = foo;
      return this;
    }

    public Builder withBar(String bar) {
      getObject().bar = bar;
      return this;
    }

    protected ParentType getObject() {
      return object;
    }

    private void setObject(ParentType object) {
      this.object = object;
    }

    public ParentType build() {
      // or clone or copy constructor if you want to stamp out multiple instances...
      ParentType tmp = getObject();
      setObject(new ParentType());
      return tmp;
    }
  }

  public String toString() {
    return "ParentType2{" +
        "foo='" + foo + '\'' +
        ", bar='" + bar + '\'' +
        '}';
  }
}

キーポイント:

  • オブジェクトをビルダーにカプセル化して、継承によって親型に保持されているオブジェクトにフィールドを設定できないようにする
  • スーパーへの呼び出しにより、スーパー タイプ ビルダー メソッドに追加されたロジック (存在する場合) がサブタイプに保持されることが保証されます。
  • 欠点は、親クラスでの偽のオブジェクト作成です...しかし、それをクリーンアップする方法については以下を参照してください
  • 一見するとはるかに理解しやすく、プロパティを転送する冗長なコンストラクターはありません。
  • ビルダーオブジェクトにアクセスする複数のスレッドがある場合...私はあなたでなくてよかったと思います:)。

編集:

偽のオブジェクト作成を回避する方法を見つけました。最初にこれを各ビルダーに追加します。

private Class whoAmI() {
  return new Object(){}.getClass().getEnclosingMethod().getDeclaringClass();
}

次に、各ビルダーのコンストラクターで:

  if (whoAmI() == this.getClass()) {
    this.obj = new ObjectToBuild();
  }

コストは、new Object(){}匿名の内部クラス用の追加のクラス ファイルです。

于 2016-03-17T03:27:09.403 に答える
1

できることの 1 つは、各クラスに静的ファクトリ メソッドを作成することです。

NutritionFacts.newBuilder()
GMOFacts.newBuilder()

この静的ファクトリ メソッドは、適切なビルダーを返します。GMOFacts.Builderaを拡張することができますがNutritionFacts.Builder、それは問題ではありません。ここでの問題は、可視性に対処することです...

于 2013-06-18T08:57:28.433 に答える
-1

次の IEEE 寄稿のRefined Fluent Builder in Javaは、問題に対する包括的なソリューションを提供します。

元の質問を継承の欠落準不変性の 2 つのサブ問題に分析し、これら 2 つのサブ問題の解決策が、Java の従来のビルダー パターンでのコードの再利用による継承のサポートにどのように開かれるかを示します。

于 2018-10-01T15:57:45.957 に答える