Шаблон 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();
    }
}

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

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

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

Сваранга Сарма
источник

Ответы:

171

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

public class NutritionFacts {
    public static class Builder {
    }
}

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

Bozho
источник
34
И, собственно, Builderесть staticв примере в книге (стр. 14, строка 10 во 2-м издании).
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каждый раз строить новое .

Майкл К
источник
12

Чтобы создать внутренний конструктор в Intellij IDEA, проверьте этот плагин: https://github.com/analytical/innerbuilder

аналитически
источник
2
Это не имеет ничего общего с заданным вопросом, но очень полезно! Хорошая находка!
Голодный Андройдер
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
Верно, если мы сосредоточимся на модели построения, я сосредоточился только на «я не понимаю, что означает сообщение», и представил одно из двух решений.
Дамиан Лещинский - Ваш
3

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

Shaun
источник
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 . Тогда все будет работать нормально.

Кришна Кирти
источник