Padrão do Construtor em Java Efetivo

137

Recentemente, comecei a ler Java eficaz por Joshua Bloch. Achei a idéia do padrão Builder [Item 2 do livro] realmente interessante. Tentei implementá-lo no meu projeto, mas houve erros de compilação. A seguir, é essencialmente o que eu estava tentando fazer:

A classe com vários atributos e sua classe de construtor:

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

Classe em que tento usar a classe acima:

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

Estou recebendo o seguinte erro do compilador:

é necessária uma instância anexa que contenha java.BuilderPattern.NutritionalFacts.Builder eficazFacts Nutritional n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Eu não entendo o que a mensagem significa. Por favor explique. O código acima é semelhante ao exemplo sugerido por Bloch em seu livro.

Swaranga Sarma
fonte

Respostas:

171

Faça do construtor uma staticclasse. Então vai funcionar. Se não for estático, exigiria uma instância de sua classe proprietária - e o objetivo é não ter uma instância dela e até proibir a criação de instâncias sem o construtor.

public class NutritionFacts {
    public static class Builder {
    }
}

Referência: Classes Aninhadas

Bozho
fonte
34
E, de fato, Builderestá staticno exemplo do livro (página 14, linha 10 na 2ª Edição).
Powerlord 15/02
27

Você deve tornar a classe Builder como estática e também tornar os campos finais e ter getters para obter esses valores. Não forneça setters para esses valores. Dessa forma, sua classe será perfeitamente imutável.

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

E agora você pode definir as propriedades da seguinte maneira:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
Raj Hassani
fonte
Por que não tornar públicos os campos NutritionalFacts? Eles já são finais e ainda seriam imutáveis.
Skip.heliou
finalOs campos só fazem sentido se os campos forem sempre necessários durante a inicialização. Caso contrário, os campos não devem ser final.
Piotrek Hryciuk
12

Você está tentando acessar uma classe não estática de maneira estática. Mude Builderpara static class Buildere deve funcionar.

O exemplo de uso que você fornece falha porque não há instância do Builderpresente. Uma classe estática para todos os fins práticos é sempre instanciada. Se você não o tornar estático, precisará dizer:

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

Porque você precisaria construir um novo Buildersempre.

Michael K
fonte
12

Para gerar um construtor interno no Intellij IDEA, confira este plugin: https://github.com/analytically/innerbuilder

analiticamente
fonte
2
Isso não tem nada a ver com a pergunta, mas é muito útil! Bom achado!
The Hungry Androider
5

Depois de ter uma idéia, na prática, você pode achar o lombok's @Buildermuito mais conveniente.

@Builder permite que você produza automaticamente o código necessário para que sua classe seja instanciada com códigos como:

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

Documentação oficial: https://www.projectlombok.org/features/Builder

torina
fonte
4

Isso significa que você não pode criar um tipo de anexo. Isso significa que primeiro você precisa selecionar uma instância da classe "pai" e, a partir dessa instância, você pode criar instâncias de classe aninhadas.

NutritionalFacts n = new NutritionalFacts()

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

Classes aninhadas

Damian Leszczyński - Vash
fonte
3
isso não faz muito sentido, porque ele precisa do construtor para construir os "fatos", e não o contrário.
Bozho 15/02/11
5
true se nos concentrarmos no padrão do construtor, foquei apenas em "Não entendo o que a mensagem significa" e apresentei uma das duas soluções.
Damian Leszczyński - Vash 15/02
3

A classe Builder deve ser estática. No momento, não tenho tempo para testar o código além disso, mas se não funcionar, avise-me e darei outra olhada.

Shaun
fonte
1

Pessoalmente, prefiro usar a outra abordagem, quando você tem 2 classes diferentes. Então você não precisa de nenhuma classe estática. Isso é basicamente para evitar a gravação Class.Builderquando você precisar criar uma nova instância.

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

Portanto, você pode usar seu construtor assim:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
impressões digitais
fonte
0

Como muitos já declarados aqui, você precisa fazer a aula static. Apenas uma pequena adição - se você quiser, existe uma maneira um pouco diferente sem a estática.

Considere isto. Implementar um construtor declarando algo como withProperty(value)setters de tipo dentro da classe e fazê-los retornar uma referência a si mesma. Nessa abordagem, você tem uma classe única e elegante, que é um segmento seguro e conciso.

Considere isto:

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

Confira mais exemplos do Java Builder .

Johnny
fonte
0

Você precisa alterar a classe Builder para a classe estática Builder . Então vai funcionar bem.

krishna kirti
fonte