効果的なJavaのビルダーパターン


137

私は最近、Joshua BlochによるEffective Javaを読み始めました。Builderパターン[本の項目2]のアイデアは本当に興味深いものでした。プロジェクトに実装しようとしましたが、コンパイルエラーが発生しました。以下は本質的に私がやろうとしていたことです:

複数の属性を持つクラスとそのビルダークラス:

public class NutritionalFacts {
    private int sodium;
    private int fat;
    private int carbo;

    public class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder(int s) {
            this.sodium = s;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

上記のクラスを使用しようとするクラス:

public class Main {
    public static void main(String args[]) {
        NutritionalFacts n = 
            new NutritionalFacts.Builder(10).carbo(23).fat(1).build();
    }
}

次のコンパイラエラーが発生します。

effectivejava.BuilderPattern.NutritionalFacts.Builderを含む囲みインスタンスが必要ですNutritionalFacts n = new NutritionalFacts.Builder(10).carbo(23).fat(1).build();

メッセージの意味がわかりません。説明してください。上記のコードは、ブロッホが彼の本で提案した例に似ています。


回答:


171

ビルダーをstaticクラスにします。その後、動作します。それが非静的である場合、それはそれを所有するクラスのインスタンスを必要とします-そしてポイントはそれのインスタンスを持たないこと、そしてビルダーなしでインスタンスを作ることを禁止することですらあります。

public class NutritionFacts {
    public static class Builder {
    }
}

リファレンス:ネストされたクラス


34
そして、実際にBuilderstatic、本の例(第2版の14ページ、10行目)にあります。
Powerlord、2011

27

Builderクラスを静的にして、フィールドをfinalにし、それらの値を取得するゲッターを用意する必要があります。これらの値のセッターを提供しないでください。このようにして、クラスは完全に不変になります。

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getFat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

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

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

そして、次のようにプロパティを設定できます。

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();

なぜNutritionalFactsフィールドを公開しないのですか?それらはすでに最終版であり、まだ不変です。
skia.heliou

finalフィールドは、フィールドが初期化中に常に必要な場合にのみ意味があります。そうでない場合、フィールドはであってはなりませんfinal
Piotrek Hryciuk

12

静的な方法で非静的クラスにアクセスしようとしています。変更Builderstatic class Builderと、それが動作するはずです。

Builderpresent のインスタンスがないため、指定した使用例は失敗します。すべての実用的な目的のための静的クラスは常にインスタンス化されます。静的にしない場合は、次のように言う必要があります。

Widget = new Widget.Builder(10).setparm1(1).setparm2(3).build();

Builder毎回新しいものを構築する必要があるからです。




5

アイデアを得たら、実際には、ロンボクの @Builderはるかに便利です。

@Builder 次のようなコードでクラスをインスタンス化できるようにするために必要なコードを自動的に生成できます。

Person.builder()
  .name("Adam Savage")
  .city("San Francisco")
  .job("Mythbusters")
  .job("Unchained Reaction")
 .build(); 

公式ドキュメント:https : //www.projectlombok.org/features/Builder


4

つまり、囲みタイプを作成することはできません。つまり、最初に「親」クラスのインスタンスを生成する必要があり、次にこのインスタンスからネストされたクラスインスタンスを作成できます。

NutritionalFacts n = new NutritionalFacts()

Builder b = new n.Builder(10).carbo(23).fat(1).build();

ネストされたクラス


3
彼はビルダーが「事実」を構築する必要があり、その逆ではないので、それはあまり意味がありません。
Bozho

5
ビルダーパターンに焦点を当てた場合はtrue、「メッセージの意味がわかりません」にのみ焦点を当て、2つの解決策のいずれかを提示しました。
DamianLeszczyński-Vash

3

Builderクラスは静的でなければなりません。現在、それ以上のコードを実際にテストする時間はありませんが、それがうまくいかない場合はお知らせください。もう一度見てみましょう。


1

個人的には、2つの異なるクラスがある場合は、他のアプローチを使用することを好みます。したがって、静的クラスは必要ありません。これは基本的にClass.Builder、新しいインスタンスを作成する必要があるときに書き込みを回避するためです。

public class Person {
    private String attr1;
    private String attr2;
    private String attr3;

    // package access
    Person(PersonBuilder builder) {
        this.attr1 = builder.getAttr1();
        // ...
    }

    // ...
    // getters and setters 
}

public class PersonBuilder (
    private String attr1;
    private String attr2;
    private String attr3;

    // constructor with required attribute
    public PersonBuilder(String attr1) {
        this.attr1 = attr1;
    }

    public PersonBuilder setAttr2(String attr2) {
        this.attr2 = attr2;
        return this;
    }

    public PersonBuilder setAttr3(String attr3) {
        this.attr3 = attr3;
        return this;
    }

    public Person build() {
        return new Person(this);
    }
    // ....
}

したがって、次のようにビルダーを使用できます。

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();

0

多くの人がここですでに述べたように、クラスを作成する必要がありますstatic。ほんの少しの追加-必要に応じて、静的な方法がない場合とは少し異なる方法があります。

このことを考慮。withProperty(value)クラス内でタイプセッターのようなものを宣言することによってビルダーを実装し、それらがそれ自体への参照を返すようにします。このアプローチでは、スレッドセーフで簡潔な単一のエレガントなクラスがあります。

このことを考慮:

public class DataObject {

    private String first;
    private String second;
    private String third;

    public String getFirst(){
       return first; 
    }

    public void setFirst(String first){
       this.first = first; 
    }

    ... 

    public DataObject withFirst(String first){
       this.first = first;
       return this; 
    }

    public DataObject withSecond(String second){
       this.second = second;
       return this; 
    }

    public DataObject withThird(String third){
       this.third = third;
       return this; 
    }
}


DataObject dataObject = new DataObject()
     .withFirst("first data")
     .withSecond("second data")
     .withThird("third data");

その他のJava Builderの例については、こちらをご覧ください。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.