Шаблон Builder в ефективній Java


137

Нещодавно я почав читати «Ефективна Java» Джошуа Блоха. Думаю, що модель «Будівельник» [Пункт 2 у книзі] виявилася дуже цікавою. Я намагався реалізувати це у своєму проекті, але були помилки компіляції. Далі, по суті, те, що я намагався зробити:

Клас з декількома атрибутами та його клас конструктора:

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

Клас, де я намагаюся використовувати вищевказаний клас:

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

Я отримую таку помилку компілятора:

екземпляр, що містить додаток, що містить эффективну.BuilderPattern.NutritionalFacts.Builder потрібен NutritionalFacts n = новий NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Я не розумію, що означає повідомлення. Будь ласка, поясніть. Наведений вище код схожий на приклад, запропонований Блохом у своїй книзі.


Відповіді:


171

Зробіть будівельника staticкласом. Тоді це спрацює. Якщо він нестатичний, він вимагатиме екземпляра власного класу - і справа не в тому, щоб мати його примірник і навіть забороняти робити екземпляри без конструктора.

public class NutritionFacts {
    public static class Builder {
    }
}

Довідка: Вкладені класи


34
І, власне, Builderє staticприклад у книзі (стор. 14, рядок 10 у другому виданні).
Powerlord

27

Ви повинні зробити клас Builder статичним, а також вам слід зробити поля остаточними та мати одержувачі, щоб отримати ці значення. Не надайте заданим значенням ці значення. Таким чином ваш клас буде ідеально непорушним.

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

А тепер ви можете встановити властивості так:

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

Чому б просто не оприлюднити поля NutritionalFacts? Вони вже остаточні, і це все одно буде непорушним.
skia.heliou

finalполя мають сенс лише у тому випадку, якщо поля завжди потрібні під час ініціалізації. Якщо ні, то полів не повинно бути final.
Піотрек Грицюк

12

Ви намагаєтесь стати статичним способом отримати доступ до нестатичного класу. Змініть Builderна, static class Builderі це має працювати.

Приклад використання, який ви наводите, не вдається, оскільки немає жодного примірника Builder. Статичний клас для всіх практичних цілей завжди примірник. Якщо ви не зробите це статичним, вам потрібно буде сказати:

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

Тому що вам потрібно було б будувати нове Builderщоразу.



8

Вам потрібно оголосити Builderвнутрішній клас як static.

Зверніться до деякої документації як для нестатичних внутрішніх класів, так і для статичних внутрішніх класів .

В основному нестатичні екземпляри внутрішніх класів не можуть існувати без приєднаного екземпляра зовнішнього класу.


5

Як тільки ви зрозуміли, на практиці вам може здатися, що ломбок @Builderнабагато зручніший.

@Builder дозволяє автоматично виробляти код, необхідний для того, щоб ваш клас був неможливим з таким кодом, як:

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

Офіційна документація: https://www.projectlombok.org/features/Builder


4

Це означає, що ви не можете створити тип закритого типу. Це означає, що спочатку потрібно створити екземпляр класу "батьків", а потім із цього примірника можна створити вкладені екземпляри класу.

NutritionalFacts n = new NutritionalFacts()

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

Вкладені класи


3
це не має особливого сенсу, тому що він потребує будівельника, щоб будувати "факти", а не навпаки.
Божо

5
правда, якщо ми зосередимось на шаблоні будівельника, я зосередився лише на "я не розумію, що означає повідомлення", і представив одне з двох рішень.
Damian Leszczyński - Vash

3

Клас Builder повинен бути статичним. Зараз у мене немає часу, щоб перевірити код поза цим, але якщо він не працює, дайте мені знати, і я погляну ще раз.


1

Я особисто вважаю за краще використовувати інший підхід, коли у вас є 2 різні класи. Тож вам не потрібен будь-який статичний клас. Це в основному, щоб уникнути запису, Class.Builderколи вам доведеться створити новий екземпляр.

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

Отже, ви можете використовувати свого конструктора так:

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

0

Як уже багато тут сказано, вам потрібно скласти клас static. Просто невелике доповнення - якщо ви хочете, існує трохи інший спосіб без статичного.

Розглянемо це. Впровадження конструктора, оголосивши щось подібне до withProperty(value)сеттерів типу всередині класу та змусити їх повертати посилання на себе. У такому підході у вас є єдиний і елегантний клас, який є безпечним і стислим для ниток.

Врахуйте це:

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

Перегляньте це на інших прикладах Java Builder .


0

Вам потрібно змінити клас Builder на статичний клас Builder . Тоді це буде добре працювати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.