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

Недавно я начал читать книгу "Эффективная 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();
    }
}

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

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

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

12 ответов

Решение

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

public class NutritionFacts {
    public static class Builder {
    }
}

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

Вы должны сделать класс 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();

Для создания внутреннего компоновщика в Intellij IDEA, проверьте этот плагин: https://github.com/analytically/innerbuilder

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

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

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

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

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

Консультируйтесь с некоторой документацией как для нестатических внутренних классов, так и для статических внутренних классов.

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

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

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

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

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

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

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

NutritionalFacts n = new NutritionalFacts()

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

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

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

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

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

Другие решения удваивают выделение памяти для создания экземпляра объекта. Следующее решение не имеет этой проблемы.

      public class NutritionalFacts{

    private int sodium;
    private int fat;
    private int carbo;

    private NutritionalFacts(){}

    public int getSodium(){ return sodium;}

    public int getFat(){ return fat;}

    public int getCarbo(){ return carbo;}

    public static class Builder{
        private NutritionalFacts nutrionalFacts;

        public Builder(){
           nutrionalFacts = new NutritionalFacts();
        }

        public Builder sodium(int s){
            nutrionalFacts.sodium = s;
            return this;
        }

        public Builder fat(int f){
            nutrionalFacts.fat = f;
            return this;
        }

        public Builder carbo(int c){
            nutrionalFacts.carbo = c;
            return this;
        }

        public NutritionalFacts build(){
            return nutrionalFacts;
        }
    }
}
Другие вопросы по тегам