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

Недавно я начал читать "Эффективную Java" Джошуа Блоха. Я нашел идею шаблона Builder [Пункт 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();
    }
}

Я получаю следующую ошибку компилятора:

содержащий экземпляр, содержащий effectivejava.BuilderPattern.NutritionalFacts.Builder необходимо                 NutritionalFacts n = новый NutritionalFacts.Builder(10).carbo(23).fat(1).build();

Я не понимаю, что означает сообщение. Пожалуйста, объясни. Вышеприведенный код похож на пример, предложенный Блохом в его книге.

Ответ 1

Сделайте строителем класс static. Тогда это сработает. Если он нестатический, для него потребуется экземпляр его собственного класса - и точка не должна иметь его экземпляр и даже запретить создание экземпляров без строителя.

public class NutritionFacts {
    public static class Builder {
    }
}

Ссылка: Вложенные классы

Ответ 2

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

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

Ответ 4

Вы пытаетесь получить нестатический класс статическим способом. Измените Builder на static class Builder, и он должен работать.

Пример использования, который вы даете, терпит неудачу, потому что нет экземпляра Builder present. Статический класс для всех практических целей всегда создается. Если вы не сделаете его статическим, вам нужно сказать:

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

Поскольку вам нужно будет каждый раз создавать новый Builder.

Ответ 5

Вам нужно объявить внутренний класс Builder как static.

Обратитесь к документации для нестатических внутренних классов и static internal классы.

В принципе, нестатические экземпляры внутренних классов не могут существовать без прикрепленного экземпляра внешнего класса.

Ответ 6

Как только у вас появится идея, на практике вы можете найти ломбок @Builder гораздо более удобным.

@Builder позволяет автоматически создавать код, необходимый для того, чтобы ваш класс мог быть создан с помощью такого кода, как:

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

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

Ответ 7

Это означает, что вы не можете создать тип enclose. Это означает, что сначала вам нужно закрепить экземпляр "родительского" класса, а затем из этого экземпляра вы можете создавать вложенные экземпляры классов.

NutritionalFacts n = new NutritionalFacts()

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

Вложенные классы

Ответ 8

Класс Builder должен быть статическим. У меня нет времени прямо сейчас, чтобы проверить код выше этого, но если он не работает, дайте мне знать, и я еще раз посмотрю.

Ответ 9

Я лично предпочитаю использовать другой подход, когда у вас есть 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();

Ответ 10

Как многие уже заявили здесь, вам нужно сделать класс 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.

Ответ 11

Вам необходимо изменить класс Builder на статический класс Builder. Тогда все будет работать нормально.