Des améliorations au modèle de conception de générateur de Joshua Bloch?


12

En 2007, j'ai lu un article sur le point de vue de Joshua Blochs sur le "modèle de générateur" et comment il pourrait être modifié pour améliorer la surutilisation des constructeurs et des setters, en particulier lorsqu'un objet a un grand nombre de propriétés, dont la plupart sont facultatives. Un bref résumé de ce modèle de conception est articulé ici .

J'ai aimé l'idée et je l'utilise depuis. Le problème avec cela, bien qu'il soit très propre et agréable à utiliser du point de vue du client, sa mise en œuvre peut être pénible! Il y a tellement d'endroits différents dans l'objet où une seule propriété est une référence, créant ainsi l'objet et l'ajout d'une nouvelle propriété prend beaucoup de temps.

Alors ... j'ai eu une idée. Tout d'abord, un exemple d'objet dans le style de Joshua Bloch:

Style de Josh Bloch:

public class OptionsJoshBlochStyle {

    private final String option1;
    private final int option2;
    // ...other options here  <<<<

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private String option1;
        private int option2;
        // other options here <<<<<

        public Builder option1(String option1) {
            this.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.option2 = option2;
            return this;
        }

        public OptionsJoshBlochStyle build() {
            return new OptionsJoshBlochStyle(this);
        }
    }

    private OptionsJoshBlochStyle(Builder builder) {
        this.option1 = builder.option1;
        this.option2 = builder.option2;
        // other options here <<<<<<
    }

    public static void main(String[] args) {
        OptionsJoshBlochStyle optionsVariation1 = new OptionsJoshBlochStyle.Builder().option1("firefox").option2(1).build();
        OptionsJoshBlochStyle optionsVariation2 = new OptionsJoshBlochStyle.Builder().option1("chrome").option2(2).build();
    }
}

Maintenant ma version "améliorée":

public class Options {

    // note that these are not final
    private String option1;
    private int option2;
    // ...other options here

    public String getOption1() {
        return option1;
    }

    public int getOption2() {
        return option2;
    }

    public static class Builder {

        private final Options options = new Options();

        public Builder option1(String option1) {
            this.options.option1 = option1;
            return this;
        }

        public Builder option2(int option2) {
            this.options.option2 = option2;
            return this;
        }

        public Options build() {
            return options;
        }
    }

    private Options() {
    }

    public static void main(String[] args) {
        Options optionsVariation1 = new Options.Builder().option1("firefox").option2(1).build();
        Options optionsVariation2 = new Options.Builder().option1("chrome").option2(2).build();

    }
}

Comme vous pouvez le voir dans ma "version améliorée", il y a 2 endroits de moins dans lesquels nous devons ajouter du code sur les propriétés d'ajout (ou les options, dans ce cas)! Le seul point négatif que je peux voir est que les variables d'instance de la classe externe ne peuvent pas être finales. Mais, la classe est toujours immuable sans cela.

Y a-t-il vraiment un inconvénient à cette amélioration de la maintenabilité? Il doit y avoir une raison pour laquelle il a répété les propriétés au sein de la classe imbriquée que je ne vois pas?


Cela semble très similaire à ma conception du modèle de générateur en C # ici .
MattDavey

Réponses:


12

Ta variation est assez sympa. Mais cela permet aux utilisateurs de faire ceci:

Options.Builder builder = new Options.Builder().option1("firefox").option2(1);
Options optionsVariation1 = builder.build();
assert optionsVariation1.getOption1().equals("firefox");
builder.option1("chrome");
assert optionsVariation1.getOption1().equals("firefox"); // FAILURE!

Ce qui défait plutôt l'objet.

Vous pouvez changer la buildméthode pour ce faire:

public Options build() {
    Options options = this.options;
    this.options = null;
    return options;
}

Ce qui empêcherait cela - tout appel à une méthode setter sur le générateur après l' buildappel échouera avec une NullPointerException. Si vous vouliez être flash, vous pouvez même tester null et lever une IllegalStateException ou quelque chose à la place. Et vous pouvez déplacer cela vers une classe de base générique où il pourrait être utilisé par tous les constructeurs.


1
Je changerais la 2ème ligne build()à: this.options = new Options();. De cette façon, les instances Options seraient immuables en toute sécurité, plus le générateur serait réutilisable en même temps.
Natix

5

Le générateur dans le modèle de Bloch peut être utilisé plusieurs fois pour générer des objets qui sont "principalement" les mêmes. De plus, les objets immuables (tous les champs sont définitifs et eux-mêmes immuables) présentent des avantages de sécurité des threads que vos modifications peuvent vaincre.


0

Si Options était effectivement clonable (je veux dire, quelle que soit l'interface clonable), vous pouvez utiliser un modèle de prototype - en avoir un dans le générateur et le cloner dans build ().

Si vous n'utilisez pas l'interface Cloneable, vous devrez copier chaque champ, donc cela ajouterait un autre endroit où vous devez l'ajouter, donc au moins pour la classe avec des champs simples utilisant Cloneable serait une bonne idée.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.