Gestion des constructeurs avec de nombreux paramètres en Java

106

Dans certains de nos projets, il existe une hiérarchie de classes qui ajoute plus de paramètres à mesure qu'elle descend dans la chaîne. En bas, certaines classes peuvent avoir jusqu'à 30 paramètres, dont 28 sont juste passés dans le super constructeur.

Je reconnais que l'utilisation de DI automatisée via quelque chose comme Guice serait bien, mais pour des raisons techniques, ces projets spécifiques sont limités à Java.

Une convention d'organisation des arguments alphabétiquement par type ne fonctionne pas car si un type est refactoré (le cercle que vous passiez pour l'argument 2 est maintenant une forme), il peut soudainement être dans le désordre.

Cette question peut être spécifique et pleine de critiques "Si c'est votre problème, vous le faites mal au niveau de la conception", mais je cherche juste des points de vue.

Steve Armstrong
la source

Réponses:

265

Le modèle de conception du constructeur peut vous aider. Prenons l'exemple suivant

public class StudentBuilder
{
    private String _name;
    private int _age = 14;      // this has a default
    private String _motto = ""; // most students don't have one

    public StudentBuilder() { }

    public Student buildStudent()
    {
        return new Student(_name, _age, _motto);
    }

    public StudentBuilder name(String _name)
    {
        this._name = _name;
        return this;
    }

    public StudentBuilder age(int _age)
    {
        this._age = _age;
        return this;
    }

    public StudentBuilder motto(String _motto)
    {
        this._motto = _motto;
        return this;
    }
}

Cela nous permet d'écrire du code comme

Student s1 = new StudentBuilder().name("Eli").buildStudent();
Student s2 = new StudentBuilder()
                 .name("Spicoli")
                 .age(16)
                 .motto("Aloha, Mr Hand")
                 .buildStudent();

Si nous omettons un champ obligatoire (vraisemblablement le nom est requis), nous pouvons demander au constructeur Student de lever une exception. Et cela nous permet d'avoir des arguments par défaut / optionnels sans avoir besoin de garder une trace de tout type d'ordre des arguments, puisque n'importe quel ordre de ces appels fonctionnera également bien.

Eli Courtwright
la source
10
Bien sûr, avec les importations statiques, vous n'avez même jamais à "voir" ces "constructeurs". Par exemple, vous pouvez avoir des méthodes statiques name (String name) qui renvoie un générateur et Student (StudentBuilder) qui renvoie un étudiant. D'où Student (nom ("Joe"). Age (15) .motto ("Je me suis mouillé"));
oxbow_lakes
2
@oxbow_lakes: Dans votre exemple, quelle classe a un nom de méthode statique (String name)?
user443854
Techniquement parlant, il est possible d'utiliser la classe Student pour créer un nouvel étudiant. J'ai ajouté les méthodes à l'intérieur de la classe Student et cela a très bien fonctionné. De cette façon, je n'avais pas besoin d'une autre classe de constructeur. Je ne suis pas sûr que cela soit souhaitable. Y a-t-il une raison d'utiliser une autre classe (StudentBuilder) pour la construire?
WVrock
1
@WVrock: Cela dépend de votre implémentation. Comme je le dis dans ma réponse, faire cela avec la classe étudiante elle-même pourrait potentiellement laisser la classe dans un état semi-initialisé, par exemple si vous avez un champ obligatoire qui n'a pas encore été initialisé.
Eli Courtwright
@EliCourtwright Je suppose que c'est une question de préférence / conception de code. Au lieu de faire lever l'exception par le constructeur, j'ai fait en sorte que la buildStudent()méthode lance l'exception.
WVrock
25

Pouvez-vous encapsuler des paramètres associés à l'intérieur d'un objet?

par exemple, si les paramètres sont comme


MyClass(String house, String street, String town, String postcode, String country, int foo, double bar) {
  super(String house, String street, String town, String postcode, String country);
  this.foo = foo;
  this.bar = bar;

alors vous pourriez avoir à la place:


MyClass(Address homeAddress, int foo, double bar) {
  super(homeAddress);
  this.foo = foo;
  this.bar = bar;
}

JeeBee
la source
14

Ce que vous voulez probablement faire, c'est avoir une classe Builder. Ensuite, vous feriez quelque chose comme ceci:

MyObject obj = new MyObjectBuilder().setXxx(myXxx)
                                    .setYyy(myYyy)
                                    .setZzz(myZzz)
                                    // ... etc.
                                    .build();

Voir page 8 et suivantes de cette présentation Josh Bloch (PDF), ou cette revue de Effective Java

Michael Myers
la source
8

Eh bien, utiliser le modèle de générateur pourrait être une solution.

Mais une fois que vous arrivez à 20 à 30 paramètres, je suppose qu'il existe une relation élevée entre les paramètres. Donc (comme suggéré) les envelopper dans des objets de données logiquement sains est probablement le plus logique. De cette façon, l'objet de données peut déjà vérifier la validité des contraintes entre les paramètres.

Pour tous mes projets dans le passé, une fois que j'en suis venu à avoir trop de paramètres (et c'était 8 pas 28!), J'ai pu nettoyer le code en créant un meilleur datamodel.


la source
4

Comme vous êtes contraint à Java 1.4, si vous voulez DI, Spring serait une option très décente. DI n'est utile que dans les endroits où les paramètres du constructeur sont des services ou quelque chose qui ne varie pas pendant l'exécution.

Si vous avez tous ces différents constructeurs en raison du fait que vous voulez des options variables sur la façon de construire un objet, vous devriez sérieusement envisager d'utiliser le modèle Builder.

Guðmundur Bjarni
la source
Les paramètres sont principalement des services comme vous l'avez mentionné, et donc DI est ce dont j'aurais besoin. Je pense que le modèle Builder mentionné dans quelques autres réponses est exactement ce que j'espérais.
Steve Armstrong
4

La meilleure solution est de ne pas avoir trop de paramètres dans le constructeur. Seuls les paramètres vraiment nécessaires dans le constructeur sont les paramètres nécessaires pour initialiser correctement l'objet. Vous pouvez avoir des constructeurs avec plusieurs paramètres, mais aussi un constructeur avec uniquement les paramètres minimum. Les constructeurs supplémentaires appellent ce constructeur simple et ensuite les setters pour définir les autres paramètres. De cette façon, vous pouvez éviter le problème de chaîne avec de plus en plus de paramètres, mais aussi avoir des constructeurs pratiques.

Mnementh
la source
1

Refactoriser pour réduire le nombre de paramètres et la profondeur de votre hiérarchie d'héritage est à peu près tout ce à quoi je peux penser car rien ne va vraiment aider à garder les paramètres de 20 quelque chose. Vous allez devoir passer chaque appel en consultant la documentation.

Une chose que vous pouvez faire est de regrouper certains paramètres regroupés logiquement dans leur propre objet de niveau supérieur, mais cela pose ses propres problèmes.

Aaron Maenpaa
la source