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.