Builder-Muster in effektivem Java

137

Ich habe kürzlich begonnen, Effective Java von Joshua Bloch zu lesen. Ich fand die Idee des Builder-Musters [Punkt 2 im Buch] wirklich interessant. Ich habe versucht, es in meinem Projekt zu implementieren, aber es gab Kompilierungsfehler. Folgendes habe ich im Wesentlichen versucht:

Die Klasse mit mehreren Attributen und ihre Builder-Klasse:

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

Klasse, in der ich versuche, die obige Klasse zu verwenden:

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

Ich erhalte den folgenden Compilerfehler:

Eine einschließende Instanz, die effektivjava.BuilderPattern.NutritionalFacts.Builder enthält, ist erforderlich. NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Ich verstehe nicht, was die Nachricht bedeutet. Bitte erkläre. Der obige Code ähnelt dem von Bloch in seinem Buch vorgeschlagenen Beispiel.

Swaranga Sarma
quelle

Antworten:

171

Machen Sie den Builder zu einer staticKlasse. Dann wird es funktionieren. Wenn es nicht statisch ist, würde es eine Instanz seiner eigenen Klasse erfordern - und es geht nicht darum, eine Instanz davon zu haben und sogar das Erstellen von Instanzen ohne den Builder zu verbieten.

public class NutritionFacts {
    public static class Builder {
    }
}

Referenz: Verschachtelte Klassen

Bozho
quelle
34
Und in der Tat Builderist staticin dem Beispiel im Buch (Seite 14, Zeile 10 in der 2. Ausgabe).
Powerlord
27

Sie sollten die Builder-Klasse als statisch festlegen und die Felder endgültig machen und Getter haben, um diese Werte abzurufen. Geben Sie keine Setter für diese Werte an. Auf diese Weise wird Ihre Klasse vollkommen unveränderlich sein.

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

Und jetzt können Sie die Eigenschaften wie folgt einstellen:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
Raj Hassani
quelle
Warum nicht einfach die Felder von NutritionalFacts veröffentlichen? Sie sind bereits endgültig und es wäre immer noch unveränderlich.
skia.heliou
finalFelder sind nur dann sinnvoll, wenn die Felder bei der Initialisierung immer benötigt werden. Wenn nicht, sollten die Felder nicht sein final.
Piotrek Hryciuk
12

Sie versuchen, statisch auf eine nicht statische Klasse zuzugreifen. Wechseln Sie Builderzu static class Builderund es sollte funktionieren.

Die von Ihnen angegebene Beispielverwendung schlägt fehl, da keine Instanz Buildervorhanden ist. Eine statische Klasse für alle praktischen Zwecke wird immer instanziiert. Wenn Sie es nicht statisch machen, müssen Sie sagen:

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

Weil Sie Builderjedes Mal ein neues bauen müssten .

Michael K.
quelle
12

Überprüfen Sie dieses Plugin, um einen inneren Builder in Intellij IDEA zu generieren: https://github.com/analytically/innerbuilder

analytisch
quelle
2
Dies hat nichts mit der gestellten Frage zu tun, ist aber sehr hilfreich! Schöner Fund!
Der hungrige Androider
8

Sie müssen die Builderinnere Klasse als deklarieren static.

Konsultieren Sie einige Dokumentationen sowohl für nicht statische innere Klassen als auch für statische innere Klassen .

Grundsätzlich können die nicht statischen Instanzen der inneren Klassen nicht ohne angehängte Instanz der äußeren Klasse existieren.

Grzegorz Oledzki
quelle
5

Sobald Sie eine Idee haben, können Sie in der Praxis Lomboks finden @Builder viel bequemer finden.

@Builder Mit dieser Option können Sie automatisch den Code erstellen, der erforderlich ist, damit Ihre Klasse mit Code wie dem folgenden instanziierbar ist:

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

Offizielle Dokumentation: https://www.projectlombok.org/features/Builder

Torina
quelle
4

Dies bedeutet, dass Sie keinen Enclose-Typ erstellen können. Dies bedeutet, dass Sie zuerst eine Instanz der "übergeordneten" Klasse zuordnen müssen und dann aus dieser Instanz verschachtelte Klasseninstanzen erstellen können.

NutritionalFacts n = new NutritionalFacts()

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

Verschachtelte Klassen

Damian Leszczyński - Vash
quelle
3
das macht nicht viel Sinn, weil er den Erbauer braucht, um die "Fakten" zu konstruieren, nicht umgekehrt.
Bozho
5
Wenn wir uns auf das Builder-Muster konzentrieren, habe ich mich nur auf "Ich verstehe nicht, was die Nachricht bedeutet" konzentriert und eine von zwei Lösungen vorgestellt.
Damian Leszczyński - Vash
3

Die Builder-Klasse sollte statisch sein. Ich habe momentan keine Zeit, den Code darüber hinaus tatsächlich zu testen, aber wenn es nicht funktioniert, lass es mich wissen und ich werde einen weiteren Blick darauf werfen.

Shaun
quelle
1

Ich persönlich bevorzuge den anderen Ansatz, wenn Sie 2 verschiedene Klassen haben. Sie benötigen also keine statische Klasse. Dies dient im Wesentlichen dazu, das Schreiben zu vermeiden, Class.Builderwenn Sie eine neue Instanz erstellen müssen.

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

Sie können Ihren Builder also folgendermaßen verwenden:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
Fingerabdrücke
quelle
0

Wie viele hier bereits angegeben haben, müssen Sie die Klasse machen static. Nur eine kleine Ergänzung - wenn Sie möchten, gibt es einen etwas anderen Weg ohne statische.

Bedenken Sie. Implementieren eines Builders durch Deklarieren von withProperty(value)Typensetzern innerhalb der Klasse und Zurückgeben eines Verweises auf sich selbst. Bei diesem Ansatz haben Sie eine einzelne und eine elegante Klasse, die threadsicher und prägnant ist.

Bedenken Sie:

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

Weitere Java Builder- Beispiele finden Sie hier.

Johnny
quelle
0

Sie müssen die Builder- Klasse in die statische Klasse Builder ändern . Dann wird es gut funktionieren.

krishna kirti
quelle