web-dev-qa-db-fra.com

Paramètres facultatifs ou constructeurs surchargés

J'implémente un DelegateCommand, et lorsque j'étais sur le point d'implémenter le ou les constructeurs, j'ai proposé les deux choix de conception suivants:

1: Avoir plusieurs constructeurs surchargés

public DelegateCommand(Action<T> execute) : this(execute, null) { }

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
    this.execute = execute;
    this.canExecute = canExecute;
}

2: Avoir un seul constructeur avec un paramètre facultatif

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute = null)
{
    this.execute = execute;
    this.canExecute = canExecute;
}

Je ne sais pas lequel utiliser parce que je ne sais pas quoi avantages/inconvénients possibles viennent avec l'une des deux façons proposées. Les deux peuvent être appelés comme ceci:

var command = new DelegateCommand(this.myExecute);
var command2 = new DelegateCommand(this.myExecute, this.myCanExecute);

Quelqu'un peut-il m'orienter dans la bonne direction et donner des commentaires?

28
Thomas Flinkow

Je préfère plusieurs constructeurs aux valeurs par défaut et personnellement je n'aime pas votre exemple à deux constructeurs, il devrait être implémenté différemment.

La raison de l'utilisation de plusieurs constructeurs est que le principal peut simplement vérifier si tous les paramètres ne sont pas nuls et s'ils sont valides tandis que d'autres constructeurs peuvent fournir des valeurs par défaut pour le principal.

Dans vos exemples cependant, il n'y a aucune différence entre eux car même le constructeur secondaire passe un null comme valeur par défaut et le constructeur principal doit également connaître la valeur par défaut. Je pense que ça ne devrait pas.

Cela signifie qu'il serait plus propre et mieux séparé s'il était implémenté de cette façon:

public DelegateCommand(Action<T> execute) : this(execute, _ => true) { }

public DelegateCommand(Action<T> execute, Func<T, bool> canExecute)
{
    this.execute = execute ?? throw new ArgumentNullException(..);
    this.canExecute = canExecute ?? throw new ArgumentNullException(..);
}

remarquez le _ => true passé au constructeur principal qui vérifie maintenant également tous les paramètres pour null et ne se soucie pas des valeurs par défaut.


Le point le plus important est cependant l'extensibilité. Plusieurs constructeurs sont plus sûrs lorsqu'il est possible que vous étendiez votre code à l'avenir. Si vous ajoutez plus de paramètres obligatoires et que ceux facultatifs doivent arriver à la fin, vous casserez toutes vos implémentations actuelles. Vous pouvez faire l'ancien constructeur [Obsolete] et informer les utilisateurs qu'elle va être supprimée, leur laissant le temps de migrer vers la nouvelle implémentation sans casser instantanément leur code.


D'un autre côté, rendre trop de paramètres facultatifs serait également source de confusion car si certains d'entre eux sont requis dans un scénario et facultatifs dans un autre, vous devrez étudier la documentation au lieu de simplement choisir le bon constructeur en regardant simplement ses paramètres.

24
t3chb0t

Étant donné que la seule chose que vous faites dans le constructeur est une affectation simple, la solution à constructeur unique peut être un meilleur choix dans votre cas. L'autre constructeur ne fournit aucune fonctionnalité supplémentaire et il ressort clairement de la conception du constructeur avec deux paramètres que le deuxième argument n'a pas besoin d'être fourni.

Plusieurs constructeurs ont un sens lorsque vous construisez un objet à partir de différents types. Dans ce cas, les constructeurs sont une substitution d'une fabrique externe car ils traitent les paramètres d'entrée et les mettent en forme pour une représentation de propriété interne correcte de la classe en cours de construction. Cela ne se produit cependant pas dans votre cas, c'est pourquoi un seul constructeur devrait être plus que suffisant.

17
Andy

Je pense qu'il y a deux cas différents pour lesquels chaque approche peut briller.

Premièrement, lorsque nous avons des constructeurs simples (ce qui est généralement le cas, selon mon expérience), je considérerais les arguments facultatifs pour briller.

  1. Ils minimisent le code qui doit être écrit (et donc aussi qui doit être lu).
  2. Vous pouvez vous assurer que la documentation est au même endroit et n'est pas répétée (ce qui est mauvais car cela ouvre une zone supplémentaire qui pourrait devenir obsolète).
  3. Si vous avez de nombreux arguments facultatifs, vous pouvez éviter d'avoir un ensemble très déroutant de combinaisons de constructeurs. Heck, même avec seulement 2 arguments facultatifs (sans rapport les uns avec les autres), si vous vouliez des constructeurs séparés et surchargés, vous devriez avoir 4 constructeurs (version sans aucun, version avec chacun et version avec les deux). Cela n'évolue évidemment pas bien.

Buuuut, il y a des cas où les arguments optionnels dans les constructeurs rendent les choses clairement plus confuses. L'exemple évident est lorsque ces arguments facultatifs sont exclusifs (c'est-à-dire qu'ils ne peuvent pas être utilisés ensemble). Le fait d'avoir des constructeurs surchargés qui n'autorisent que les combinaisons d'arguments réellement valides garantirait l'application de la contrainte de temps de compilation de cette contrainte. Cela dit, vous devez également éviter de rencontrer ce cas (par exemple, avec plusieurs classes héritant d'une base, où chaque classe fait le comportement exclusif).

3
Kat