1. ホーム
  2. java

[解決済み] Java Builder クラスのサブクラス化

2022-05-31 12:52:52

質問

与える この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.Builder ではなく GMOFacts.Builder この問題をどのように解決すればよいのでしょうか、あるいはもっとよいパターンがあるのでしょうか?

注意してください。 同様の質問に対するこの回答 は、私が上に持っているクラスを提供します。私の質問は、ビルダーの呼び出しが正しい順序であることを保証する問題に関してです。

どのように解決するのですか?

ジェネリックスを使って解決できます。というものだと思います。 "不思議なほど繰り返されるジェネリックパターン"

基底クラスビルダのメソッドの戻り値の型をジェネリックの引数にする。

public class NutritionFacts {

    private final int calories;

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

        private int calories = 0;

        public Builder() {}

        public T calories(int val) {
            calories = val;
            return (T) 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<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;
    }
}