Paramètres facultatifs ou constructeurs surchargés

28

J'implémente un DelegateCommand, et quand 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 quels 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?

Thomas Flinkow
la source
4
KISS et YAGNI. En cas de doute, appliquez les deux. Après un certain temps, effectuez une recherche de références aux deux constructeurs. Cela ne me surprendrait pas si l'un d'eux n'était jamais consommé à l'extérieur. Ou légèrement consommé. C'est quelque chose de facile à trouver en effectuant une analyse statique du code.
Laiv
1
Les constructeurs statiques (comme Bitmap.FromFile) sont également une option
BlueRaja - Danny Pflughoeft
3
Gardez la règle d'analyse de code CA1026: Les paramètres par défaut ne doivent pas être utilisés à l'esprit.
Dan Lyons
2
@Laiv suis-je en train de manquer quelque chose? Ils sont utilisés de la même manière et ont donc la même signature. Vous ne pouvez pas rechercher de références et savoir à quoi l'appelant pensait. Cela ressemble plus à ce que vous faites référence à savoir si OP devrait ou non avoir ce deuxième argument, mais ce n'est pas ce que OP demande (et ils peuvent bien savoir avec certitude qu'ils ont parfois besoin des deux, donc ils demandent).
Kat
2
@Voo Le compilateur pour Openedge | Progress 10.2B les ignore. À peu près tué notre stratégie de changement de méthode incassable; à la place, nous devions utiliser la surcharge pour le même effet.
Bret

Réponses:

24

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 pas de différence entre eux car même le constructeur secondaire passe a nullcomme 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(..);
}

notez le _ => truepassé au constructeur principal qui vérifie maintenant également tous les paramètres nullet ne se soucie pas des défauts.


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 créer l'ancien constructeur [Obsolete]et informer les utilisateurs qu'il va être supprimé en leur donnant 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.

t3chb0t
la source
12
Par contre, rendre trop de paramètres optionnels serait déroutant ... Pour être honnête, avoir trop de paramètres (qu'ils soient optionnels ou non) est déroutant.
Andy
1
@DavidPacker Je suis d'accord avec vous, mais combien de paramètres sont trop nombreux est une autre histoire, je pense ;-)
t3chb0t
2
Il existe une autre différence entre un constructeur qui omet un paramètre et un constructeur qui a une valeur par défaut pour le paramètre. Dans le premier cas, l'appelant peut explicitement éviter de transmettre le paramètre, alors que dans le second cas, l'appelant ne peut pas - car le fait de ne passer aucun paramètre revient à transmettre la valeur par défaut. Si le constructeur a besoin de faire cette distinction, alors deux constructeurs sont le seul chemin à parcourir.
Gary McGill
1
Par exemple, supposons que j'ai une classe qui formate les chaînes, que je peux éventuellement construire avec un CultureInfoobjet. Je préférerais une API permettant au CultureInfoparamètre d'être fourni via un paramètre supplémentaire, mais j'ai insisté sur le fait que s'il était fourni, il ne devrait pas l' être null. De cette façon, les nullvaleurs accidentelles ne sont pas mal interprétées comme signifiant «aucune».
Gary McGill
Je ne suis pas sûr que l'extensibilité soit vraiment une raison contre les paramètres facultatifs; si vous avez besoin de paramètres et que vous en modifiez le nombre, vous devrez créer un nouveau constructeur et Obsoletel'ancien si vous voulez éviter de casser des choses, que vous ayez également des paramètres facultatifs ou non. Avec les paramètres facultatifs, vous ne devez le faire que Obsoletesi vous en supprimez un.
Herohtar
17

É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.

Andy
la source
3

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).

Kat
la source
+1 pour avoir mentionné l'explosion de permutation avec plusieurs paramètres facultatifs.
Jpsy