160 votes

Combien d'arguments du constructeur est de trop?

Disons que vous avez une classe appelée Client, qui contient les champs suivants:

  • Nom d'utilisateur
  • E-mail
  • Prénom
  • Nom De Famille

Disons aussi que, selon une logique d'entreprise, tous les objets de Client doit avoir ces quatre propriétés définies.

Maintenant, on peut le faire assez facilement en obligeant le constructeur à préciser chacun de ces propriétés. Mais il est assez facile de voir comment cela peut devenir une spirale hors de contrôle lorsque vous êtes forcé d'ajouter plus de champs nécessaires à l'objet Client.

J'ai vu des classes qui prennent en 20+ arguments en leur constructeur et c'est juste une douleur à utiliser. Mais, sinon, si vous n'avez pas besoin de ces champs, vous courez le risque d'avoir pas défini d'informations, ou pour le pire, le référencement d'un objet erreurs si vous comptez sur le code appelant à spécifier ces propriétés.

Existe-il des alternatives à cette ou pensez-vous que vous avez juste à décider si X montant des arguments du constructeur, c'est trop pour vous de vivre?

139voto

toolkit Points 27248

Deux approches de conception à considérer

L' essence modèle

L' interface fluide motif

Ce sont à la fois semblables dans leur intention, en qui nous avons lentement construire un objet intermédiaire, et ensuite créer notre objet cible en une seule étape.

Un exemple de l'interface fluide dans l'action serait:

public class CustomerBuilder {
    String surname;
    String firstName;
    String ssn;
    public static CustomerBuilder customer() {
        return new CustomerBuilder();
    }
    public CustomerBuilder withSurname(String surname) {
        this.surname = surname; 
        return this; 
    }
    public CustomerBuilder withFirstName(String firstName) {
        this.firstName = firstName;
        return this; 
    }
    public CustomerBuilder withSsn(String ssn) {
        this.ssn = ssn; 
        return this; 
    }
    // client doesn't get to instantiate Customer directly
    public Customer build() {
        return new Customer(this);            
    }
}

public class Customer {
    private final String firstName;
    private final String surname;
    private final String ssn;

    Customer(CustomerBuilder builder) {
        if (builder.firstName == null) throw new NullPointerException("firstName");
        if (builder.surname == null) throw new NullPointerException("surname");
        if (builder.ssn == null) throw new NullPointerException("ssn");
        this.firstName = builder.firstName;
        this.surname = builder.surname;
        this.ssn = builder.ssn;
    }

    public String getFirstName() { return firstName;  }
    public String getSurname() { return surname; }
    public String getSsn() { return ssn; }    
}


import static com.acme.CustomerBuilder.customer;

public class Client {
    public void doSomething() {
        Customer customer = customer()
            .withSurname("Smith")
            .withFirstName("Fred")
            .withSsn("123XS1")
            .build();
    }
}

41voto

Kazark Points 2453

Je vois que certaines personnes recommandent de sept comme une limite supérieure. Apparemment, il n'est pas vrai que les gens peuvent tenir sept choses dans leur tête à la fois; ils ne peuvent se rappeler de quatre (Susan Weinschenk, 100 Choses à Chaque Concepteur a Besoin de Savoir sur Personnes, 48). De même, je considère que quatre à être quelque chose d'une grande orbite autour de la terre. Mais c'est parce que ma pensée a été irrémédiablement altéré par Bob Martin.

Dans le Code Propre, Oncle Bob plaide pour trois comme supérieur général de limite pour le nombre de paramètres. Il fait de la radicalité de la demande (40):

Le nombre idéal d'arguments pour une fonction est égale à zéro (niladiques). Vient ensuite un (monadique), suivie de près par deux (dyadique). Trois arguments (triadique) doit être évitée autant que possible. Plus de trois (polyadic) nécessite très spécial justification—et puis, ne doit pas être utilisé de toute façon.

Il dit cela à cause de la lisibilité, mais aussi du fait de la testabilité:

Imaginez la difficulté de l'écriture des cas de tests pour s'assurer que toutes les différentes combinaisons d'arguments fonctionner correctement.

Je vous encourage fortement à en trouver une copie de son livre et à la lecture de sa pleine discussion des arguments de la fonction (40-43).

Je suis d'accord avec ceux qui ont mentionné le Principe de Responsabilité Unique. Il est difficile pour moi de croire qu'une classe qui a besoin de plus que deux ou trois valeurs/des objets sans valeurs par défaut raisonnables a vraiment qu'une seule responsabilité, et ne serait pas mieux avec une autre classe extrait.

Maintenant, si vous êtes l'injection de vos dépendances par le constructeur, Bob Martin arguments au sujet de combien il est facile d'invoquer le constructeur ne pas tellement s'appliquent (parce que d'habitude, alors il existe un seul point dans votre application où vous fil, ou vous avez même un cadre qui le fait pour vous). Toutefois, le Principe de Responsabilité Unique est toujours d'actualité: une fois qu'une classe a quatre dépendances, je considère que l'odeur qu'elle est en train de faire une grande quantité de travail.

Cependant, comme avec toutes les choses dans l'informatique, il y a sans doute valides pour avoir un grand nombre de paramètres du constructeur. Ne pas déformer votre code afin d'éviter à l'aide d'un grand nombre de paramètres, mais si vous utilisez un grand nombre de paramètres, d'arrêter et de lui donner une certaine pensée, car il peut signifier que votre code est déjà tordu.

16voto

Marcio Aguiar Points 6715

Dans votre cas, s'en tenir avec le constructeur. L'information appartient à la Clientèle et les 4 champs sont beaux.

Dans le cas où vous avez de nombreux champs obligatoires et facultatifs le constructeur n'est pas la meilleure solution. @Boojiboy dit, c'est difficile à lire et il est aussi difficile d'écrire du code client.

@contagieuse a suggéré d'utiliser le modèle par défaut et les setters pour facultatif des attributs. Que les mandats que les champs sont mutables, mais c'est un problème mineur.

Joshua Bloc sur l'efficacité de Java 2-dire que, dans ce cas, vous devriez envisager un constructeur. Un exemple tiré du livre:

 public class NutritionFacts {  
   private final int servingSize;  
   private final int servings;  
   private final int calories;  
   private final int fat;  
   private final int sodium;  
   private final int carbohydrate;  

   public static class Builder {  
     // required parameters  
     private final int servingSize;  
     private final int servings;  

     // optional parameters  
     private int calories         = 0;  
     private int fat              = 0;  
     private int carbohydrate     = 0;  
     private int sodium           = 0;  

     public Builder(int servingSize, int servings) {  
      this.servingSize = servingSize;  
       this.servings = servings;  
    }  

     public Builder calories(int val)  
       { calories = val;       return this; }  
     public Builder fat(int val)  
       { fat = val;            return this; }  
     public Builder carbohydrate(int val)  
       { carbohydrate = val;   return this; }  
     public Builder sodium(int val)  
       { sodium = val;         return this; }  

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

   private NutritionFacts(Builder builder) {  
     servingSize       = builder.servingSize;  
     servings          = builder.servings;  
     calories          = builder.calories;  
     fat               = builder.fat;  
     soduim            = builder.sodium;  
     carbohydrate      = builder.carbohydrate;  
   }  
}

Et puis l'utiliser comme ceci:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).
      calories(100).sodium(35).carbohydrate(27).build();

L'exemple ci-dessus a été prise à partir Effective Java 2

Et cela ne s'applique uniquement à un constructeur. Citant Kent Beck dans la mise en Œuvre des Schémas:

setOuterBounds(x, y, width, height);
setInnerBounds(x + 2, y + 2, width - 4, height - 4);

Le rectangle explicite comme un objet, explique le code mieux:

setOuterBounds(bounds);
setInnerBounds(bounds.expand(-2));

10voto

OwenP Points 11164

Je pense que le "pur OOP", la réponse est que si les opérations de la classe ne sont pas valides lorsque certains membres ne sont pas initialisé, alors ces membres doit être défini par le constructeur. Il y a toujours les cas où les valeurs par défaut peuvent être utilisés, mais je vais supposer que nous n'êtes pas tenu de cette affaire. C'est une bonne approche lorsque l'API est fixe, car la seule admissible constructeur après l'API va public va être un cauchemar pour vous et tous les utilisateurs de votre code.

En C#, ce que je comprends sur les lignes directrices de conception, c'est que ce n'est pas nécessairement la seule façon de gérer la situation. En particulier avec des objets WPF, vous trouverez que .NET les classes ont tendance à favoriser sans paramètre constructeurs et lève des exceptions si les données n'ont pas été initialisé à un état souhaité avant d'appeler la méthode. C'est probablement surtout spécifiques à la composante de base de la conception de bien; je ne peux pas venir avec un exemple concret d'une .NET de classe qui se comporte de cette manière. Dans votre cas, ce serait certainement causer une augmentation de la charge sur des tests pour s'assurer que la classe n'est jamais enregistré à la banque de données, à moins que les propriétés ont été validées. Honnêtement, de ce fait, je préfère le "constructeur définit les propriétés nécessaires à l'approche du" si votre API est définie dans la pierre ou pas du public.

La seule chose que je suis certain, c'est qu'il y a probablement d'innombrables méthodes pour résoudre ce problème, et chacun d'eux présente son propre ensemble de problèmes. La meilleure chose à faire est d'apprendre autant de modèles que possible et de choisir le meilleur pour le job. (N'est-ce pas un tel cop-out d'une réponse?)

5voto

vitule Points 3464

Je pense que votre question est plus sur la conception de vos classes que sur le nombre d'arguments dans le constructeur. Si j'ai besoin de 20 morceaux de données (arguments) pour réussir à initialiser un objet, je serais probablement envisager de diviser la classe.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X