Builder Pattern in Java efficace


137

Di recente ho iniziato a leggere Effective Java di Joshua Bloch. Ho trovato davvero interessante l'idea del modello Builder [Item 2 nel libro]. Ho provato a implementarlo nel mio progetto ma ci sono stati errori di compilazione. Di seguito è in sostanza quello che stavo cercando di fare:

La classe con più attributi e la sua classe builder:

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

Classe in cui provo a utilizzare la classe sopra:

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

Ricevo il seguente errore del compilatore:

un'istanza chiusa che contiene effectjava.BuilderPattern.NutritionalFacts.Builder è richiesto NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Non capisco cosa significhi il messaggio. Spiega per favore. Il codice sopra è simile all'esempio suggerito da Bloch nel suo libro.


Risposte:


171

Rendi il costruttore una staticclasse. Quindi funzionerà. Se non è statico, richiederebbe un'istanza della sua classe proprietaria - e il punto non è di avere un'istanza di essa, e persino vietare di creare istanze senza il builder.

public class NutritionFacts {
    public static class Builder {
    }
}

Riferimento: classi nidificate


34
E, in effetti, Builderè staticnell'esempio del libro (pagina 14, riga 10 della 2a edizione).
Powerlord,

27

Dovresti rendere la classe Builder come statica e anche rendere i campi definitivi e disporre di getter per ottenere quei valori. Non fornire setter a tali valori. In questo modo la tua classe sarà perfettamente immutabile.

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

E ora puoi impostare le proprietà come segue:

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

Perché non rendere pubblici i campi di NutritionalFacts? Sono già definitivi e sarebbe ancora immutabile.
skia.heliou,

finali campi hanno senso solo se i campi sono sempre necessari durante l'inizializzazione. In caso contrario, i campi non dovrebbero essere final.
Piotrek Hryciuk,

12

Stai provando ad accedere a una classe non statica in modo statico. Cambia Builderinstatic class Builder e dovrebbe funzionare.

L'uso di esempio fornito non ha esito positivo Builder. Viene sempre istanziata una classe statica per tutti gli scopi pratici. Se non lo rendi statico, dovresti dire:

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

Perché dovresti costruirne uno nuovo Builderogni volta.




5

Una volta che hai un'idea, in pratica, potresti trovare @Buildermolto più conveniente il lombok .

@Builder ti consente di produrre automaticamente il codice necessario affinché la tua classe sia istantanea con codice come:

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

Documentazione ufficiale: https://www.projectlombok.org/features/Builder


4

Ciò significa che non è possibile creare un tipo allegato. Ciò significa che prima devi certificare un'istanza della classe "parent" e poi da questa istanza puoi creare istanze di classe nidificate.

NutritionalFacts n = new NutritionalFacts()

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

Classi nidificate


3
non ha molto senso, perché ha bisogno del costruttore per costruire i "fatti", non viceversa.
Bozho,

5
vero se ci concentriamo sul modello del costruttore, mi sono concentrato solo su "Non capisco cosa significhi il messaggio" e ho presentato una delle due soluzioni.
Damian Leszczyński - Vash,

3

La classe Builder dovrebbe essere statica. Non ho tempo in questo momento per testare effettivamente il codice oltre quello, ma se non funziona fammelo sapere e darò un'altra occhiata.


1

Personalmente preferisco usare l'altro approccio, quando hai 2 classi diverse. Quindi non hai bisogno di alcuna classe statica. Questo in pratica è per evitare di scrivere Class.Builderquando devi creare una nuova istanza.

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

Quindi, puoi usare il tuo builder in questo modo:

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

0

Come molti hanno già affermato qui, devi fare la lezione static. Solo una piccola aggiunta - se vuoi, c'è un modo un po 'diverso senza uno statico.

Considera questo. Implementare un builder dichiarando qualcosa come i withProperty(value)setter di tipi all'interno della classe e farli restituire un riferimento a se stesso. In questo approccio, hai una classe singola ed elegante che è un thread sicuro e conciso.

Considera questo:

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

Dai un'occhiata per ulteriori esempi di Java Builder .


0

È necessario modificare la classe Builder in Builder di classe statica . Quindi funzionerà bene.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.