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.

Swaranga Sarma
źródło
1
wymagany jest
Joshua Taylor

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

Bozho
źródło
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();
Raj Hassani
źródło
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 .

Michael K.
źródło
12

Aby wygenerować wewnętrzny kreator w Intellij IDEA, sprawdź tę wtyczkę: https://github.com/analytically/innerbuilder

analitycznie
źródło
2
Nie ma to nic wspólnego z zadanym pytaniem, ale jest bardzo pomocne! Niezłe znalezisko!
The Hungry Androider
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

torina
źródło
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

Damian Leszczyński - Vash
źródło
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.

Shaun
źródło
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();
odciski palców
źródło
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 .

Jasio
źródło
0

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

krishna kirti
źródło