Шаблон 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 = new NutritionalFacts.Builder(10).carbo(23).fat(1).build();

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


person Swaranga Sarma    schedule 15.02.2011    source источник
comment
возможный дубликат Требуется вложенный экземпляр, который содержит ‹my reference›   -  person Joshua Taylor    schedule 26.09.2013


Ответы (11)


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

public class NutritionFacts {
    public static class Builder {
    }
}

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

person Bozho    schedule 15.02.2011
comment
И на самом деле Builder это static в примере из книги (стр. 14, строка 10 во 2-м издании). - person Powerlord; 15.02.2011

Вы должны сделать класс 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();
person Raj Hassani    schedule 31.07.2015
comment
Почему бы просто не сделать поля NutritionalFacts общедоступными? Они уже окончательные, и это все равно будет неизменным. - person skia.heliou; 12.03.2018
comment
Поля final имеют смысл только в том случае, если поля всегда нужны во время инициализации. Если нет, то поля не должны быть final. - person Piotrek Hryciuk; 28.01.2019

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

person analytically    schedule 31.01.2014
comment
Это не имеет ничего общего с заданным вопросом, но очень полезно! Хорошая находка! - person The Hungry Androider; 15.08.2014

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

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

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

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

person Michael K    schedule 15.02.2011

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

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

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

person Grzegorz Oledzki    schedule 15.02.2011

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

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

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

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

person torina    schedule 10.11.2018

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

NutritionalFacts n = new NutritionalFacts()

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

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

person Damian Leszczyński - Vash    schedule 15.02.2011
comment
это не имеет особого смысла, потому что ему нужно, чтобы строитель конструировал факты, а не наоборот. - person Bozho; 15.02.2011
comment
правда, если мы сосредоточимся на шаблоне строителя, я сосредоточился только на том, что я не понимаю, что означает сообщение, и представил одно из двух решений. - person Damian Leszczyński - Vash; 15.02.2011

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

person Shaun    schedule 15.02.2011

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

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

person Johnny    schedule 08.08.2019

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

person krishna kirti    schedule 11.08.2019