web-dev-qa-db-fra.com

Modèle de générateur en vigueur Java

J'ai récemment commencé à lire Effective Java de Joshua Bloch. J'ai trouvé l'idée du motif Builder [Point 2 du livre] très intéressante. J'ai essayé de l'implémenter dans mon projet, mais il y en avait Ce qui suit est essentiellement ce que j’essayais de faire:

La classe avec plusieurs attributs et sa classe de générateur:

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 où j'essaie d'utiliser la classe ci-dessus:

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

Je reçois l'erreur suivante du compilateur:

une instance englobante contenant effectivejava.BuilderPattern.NutritionalFacts.Builder est nécessaire NutritionalFacts n = new NutritionalFacts.Builder (10) .carbo (23) .fat (1) .build ();

Je ne comprends pas ce que signifie le message. S'il vous plaît, expliquez. Le code ci-dessus est similaire à l'exemple proposé par Bloch dans son livre.

136
Swaranga Sarma

Faites du constructeur une classe static. Alors ça va marcher. Si elle est non statique, elle nécessiterait une instance de sa classe propriétaire. L'important est de ne pas en avoir une instance, ni même d'interdire la création d'instances sans le générateur.

public class NutritionFacts {
    public static class Builder {
    }
}

Référence: classes imbriquées

166
Bozho

Vous devez rendre la classe Builder statique, rendre les champs finaux et disposer de getters pour obtenir ces valeurs. Ne fournissez pas de paramètres à ces valeurs. De cette façon, votre classe sera parfaitement immuable.

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

Et maintenant, vous pouvez définir les propriétés comme suit:

NutritionalFacts n = new NutritionalFacts.Builder().sodium(10).carbo(15).
fat(5).build();
27
Raj Hassani

Pour générer un constructeur interne dans Intellij IDEA, consultez ce plugin: https://github.com/analytically/innerbuilder

13
analytically

Vous essayez d'accéder à une classe non statique de manière statique. Remplacez Builder par static class Builder et ça devrait marcher.

L'exemple d'utilisation que vous donnez échoue car il n'y a pas d'instance de Builder. Une classe statique à toutes fins pratiques est toujours instanciée. Si vous ne le rendez pas statique, vous devez dire:

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

Parce que vous auriez besoin de construire un nouveau Builder à chaque fois.

11
Michael K

Vous devez déclarer la classe interne Builder sous la forme static.

Consultez la documentation de classes internes non statiques et classes internes statiques .

Fondamentalement, les instances de classes internes non statiques ne peuvent exister sans l'instance de classe externe attachée.

7
Grzegorz Oledzki

Une fois que vous avez une idée, dans la pratique, vous pouvez trouver le @Builder beaucoup plus pratique.

@Builder vous permet de générer automatiquement le code requis pour que votre classe soit instanciable avec un code tel que:

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

Documentation officielle: https://www.projectlombok.org/features/Builder

5
torina

Cela signifie que vous ne pouvez pas créer de type enclose. Cela signifie que vous devez d'abord créer une instance de classe "parent", puis créer à partir de cette instance des instances de classe imbriquées.

NutritionalFacts n = new NutritionalFacts()

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

classes imbriquées

La classe Builder doit être statique. Je n'ai pas le temps de tester le code au-delà de cela, mais s'il ne fonctionne pas, faites-le-moi savoir et je l'examinerai à nouveau.

4
Shaun

Personnellement, je préfère utiliser l’autre approche, lorsque vous avez 2 classes différentes. Donc, vous n'avez besoin d'aucune classe statique. C’est essentiellement pour éviter d’écrire Class.Builder lorsque vous devez créer une nouvelle instance.

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

Donc, vous pouvez utiliser votre constructeur comme ceci:

Person person = new PersonBuilder("attr1")
                            .setAttr2("attr2")
                            .build();
1
winter

Vous devez changer la classe Builder en Builder de classe statique. Ensuite, cela fonctionnera bien.

0
krishna kirti

Comme beaucoup l'ont déjà indiqué, vous devez faire en sorte que la classe static. Juste un petit ajout - si vous voulez, il y a une manière un peu différente sans statique.

Considère ceci. Implémenter un générateur en déclarant quelque chose comme withProperty(value) des modificateurs de type à l'intérieur de la classe et leur faire renvoyer une référence à elle-même. Dans cette approche, vous avez une classe unique et une classe élégante qui est un thread sûr et concis.

Considère ceci:

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

Jetez un œil à d'autres exemples Java Builder .

0
Johnny