Wzorzec konstruktora w efektywnej Javie


137

Niedawno zacząłem czytać Efektywną Javę Joshuy Blocha. Pomysł wzorca Builder [punkt 2 w książce] wydał mi się bardzo interesujący. Próbowałem zaimplementować to w swoim projekcie, ale wystąpiły błędy kompilacji. Oto w istocie to, co próbowałem zrobić:

Klasa z wieloma atrybutami i jej klasa konstruktora:

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

Klasa, w której staram się użyć powyższej klasy:

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

Otrzymuję następujący błąd kompilatora:

otaczająca instancja, która zawiera efektywne fakty.BuilderPattern.NutritionalFacts.Builder jest wymagana NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Nie rozumiem, co oznacza ta wiadomość. Proszę wytłumacz. Powyższy kod jest podobny do przykładu zasugerowanego przez Blocha w swojej książce.


Odpowiedzi:


171

Uczyń budowniczego staticklasą. Wtedy zadziała. Jeśli nie jest statyczny, wymagałby instancji klasy będącej jej właścicielem - a chodzi o to, aby nie mieć jej instancji, a nawet zabronić tworzenia instancji bez konstruktora.

public class NutritionFacts {
    public static class Builder {
    }
}

Odniesienie: klasy zagnieżdżone


34
I faktycznie, Builderjest staticto przykład w książce (str. 14, wiersz 10 w 2. wydaniu).
Powerlord

27

Powinieneś ustawić klasę Builder jako statyczną, a także powinieneś sprawić, by pola były ostateczne i mieć metody pobierające, aby uzyskać te wartości. Nie podawaj ustawiania tych wartości. W ten sposób Twoja klasa będzie niezmienna.

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

Teraz możesz ustawić właściwości w następujący sposób:

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

Dlaczego nie upublicznić pól NutritionalFacts? Są już ostateczne i nadal byłoby niezmienne.
skia.heliou

finalpola mają sens tylko wtedy, gdy są one zawsze potrzebne podczas inicjalizacji. Jeśli nie, to pola nie powinny być final.
Piotrek Hryciuk

12

Próbujesz uzyskać dostęp do klasy niestatycznej w sposób statyczny. Zmień Buildernastatic class Builder i powinno działać.

Przykładowe użycie, które podajesz, zawodzi, ponieważ nie ma żadnego wystąpienia Builder. Klasa statyczna do wszystkich celów praktycznych jest zawsze tworzona. Jeśli nie zrobisz tego statycznym, musisz powiedzieć:

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

Ponieważ za Builderkażdym razem musiałbyś konstruować nowy .




5

Kiedy już wpadniesz na pomysł, w praktyce może się okazać, że lombok będzie o @Builderwiele wygodniejszy.

@Builder umożliwia automatyczne tworzenie kodu wymaganego do tworzenia instancji Twojej klasy za pomocą kodu, takiego jak:

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

Oficjalna dokumentacja: https://www.projectlombok.org/features/Builder


4

Oznacza to, że nie możesz utworzyć typu zamkniętego. Oznacza to, że najpierw musisz utworzyć instancję klasy „nadrzędnej”, a następnie z tej instancji możesz utworzyć instancje klas zagnieżdżonych.

NutritionalFacts n = new NutritionalFacts()

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

Klasy zagnieżdżone


3
to nie ma większego sensu, ponieważ on potrzebuje konstruktora, aby skonstruował „fakty”, a nie odwrotnie.
Bozho

5
to prawda, jeśli skupimy się na wzorcu konstruktora, skupiłem się tylko na „Nie rozumiem, co oznacza komunikat” i przedstawiłem jedno z dwóch rozwiązań.
Damian Leszczyński - Vash

3

Klasa Builder powinna być statyczna. Nie mam teraz czasu, aby faktycznie przetestować kod poza tym, ale jeśli to nie zadziała, daj mi znać, a przyjrzę się jeszcze raz.


1

Osobiście wolę zastosować inne podejście, gdy masz 2 różne klasy. Więc nie potrzebujesz żadnej statycznej klasy. Zasadniczo ma to na celu uniknięcie pisania, Class.Buildergdy musisz utworzyć nową instancję.

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

Możesz więc użyć swojego kreatora w następujący sposób:

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

0

Jak wielu już tutaj powiedziało, musisz stworzyć klasę static. Tylko mały dodatek - jeśli chcesz, jest trochę inny sposób bez statycznego.

Rozważ to. Implementowanie konstruktora przez zadeklarowanie czegoś podobnego do withProperty(value)ustawiaczy typów wewnątrz klasy i sprawienie, aby zwracały odwołanie do siebie. W tym podejściu masz pojedynczą i elegancką klasę, która jest bezpiecznym i zwięzłym wątkiem.

Rozważ to:

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

Sprawdź to, aby uzyskać więcej przykładów Java Builder .


0

Musisz zmienić klasę Builder na statyczną klasę Builder . Wtedy będzie działać dobrze.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.