J'ai cette fonction API:
public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d,
string e, string f, out Guid code)
Je n'aime pas ça. Parce que l'ordre des paramètres devient inutilement significatif. Il devient plus difficile d'ajouter de nouveaux champs. Il est plus difficile de voir ce qui se passe. Il est plus difficile de refactoriser la méthode en parties plus petites car cela crée une autre surcharge de transmission de tous les paramètres dans les sous-fonctions. Le code est plus difficile à lire.
J'ai eu l'idée la plus évidente: avoir un objet encapsulant les données et le faire circuler au lieu de passer chaque paramètre un par un. Voici ce que j'ai trouvé:
public class DoSomeActionParameters
{
public string A;
public string B;
public DateTime C;
public OtherEnum D;
public string E;
public string F;
}
Cela a réduit ma déclaration API à:
public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)
Agréable. Cela semble très innocent, mais nous avons en fait introduit un énorme changement: nous avons introduit la mutabilité. Parce que ce que nous faisions auparavant était en fait de passer un objet anonyme immuable: des paramètres de fonction sur la pile. Maintenant, nous avons créé une nouvelle classe qui est très modifiable. Nous avons créé la possibilité de manipuler l'état de l' appelant . Ça craint. Maintenant je veux que mon objet soit immuable, que dois-je faire?
public class DoSomeActionParameters
{
public string A { get; private set; }
public string B { get; private set; }
public DateTime C { get; private set; }
public OtherEnum D { get; private set; }
public string E { get; private set; }
public string F { get; private set; }
public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d,
string e, string f)
{
this.A = a;
this.B = b;
// ... tears erased the text here
}
}
Comme vous pouvez le voir, j'ai recréé mon problème d'origine: trop de paramètres. Il est évident que ce n'est pas la voie à suivre. Qu'est ce que je vais faire? La dernière option pour obtenir une telle immuabilité est d'utiliser une structure "en lecture seule" comme celle-ci:
public struct DoSomeActionParameters
{
public readonly string A;
public readonly string B;
public readonly DateTime C;
public readonly OtherEnum D;
public readonly string E;
public readonly string F;
}
Cela nous permet d'éviter les constructeurs avec trop de paramètres et d'atteindre l'immuabilité. En fait, il résout tous les problèmes (ordre des paramètres, etc.). Encore:
- Tout le monde (y compris FXCop et Jon Skeet) convient que l' exposition des champs publics est mauvaise .
- Eric Lippert et al disent que s'appuyer sur des champs en lecture seule pour l'immuabilité est un mensonge .
C'est à ce moment-là que j'ai été confus et j'ai décidé d'écrire cette question: Quelle est la manière la plus simple en C # d'éviter le problème de «trop de paramètres» sans introduire de mutabilité? Est-il possible d'utiliser une structure en lecture seule à cette fin sans avoir une mauvaise conception d'API?
CLARIFICATIONS:
- Veuillez supposer qu'il n'y a pas de violation du principe de responsabilité unique. Dans mon cas d'origine, la fonction écrit simplement des paramètres donnés dans un seul enregistrement DB.
- Je ne cherche pas une solution spécifique à la fonction donnée. Je recherche une approche généralisée de ces problèmes. Je suis spécifiquement intéressé à résoudre le problème de "trop de paramètres" sans introduire de mutabilité ou une conception terrible.
METTRE À JOUR
Les réponses fournies ici présentent différents avantages / inconvénients. Par conséquent, j'aimerais convertir cela en un wiki communautaire. Je pense que chaque réponse avec un exemple de code et des avantages / inconvénients ferait un bon guide pour des problèmes similaires à l'avenir. J'essaie maintenant de savoir comment faire.
DoSomeActionParameters
est un objet jetable, qui sera jeté après l'appel de la méthode.Réponses:
Un style adopté dans les frameworks est généralement comme le regroupement des paramètres liés dans des classes associées (mais encore une fois problématique avec la mutabilité):
la source
Utilisez une combinaison de générateur et d'API de style de langage spécifique au domaine - Interface Fluent. L'API est un peu plus verbeuse mais avec intellisense, elle est très rapide à taper et facile à comprendre.
la source
DoSomeAction
était une méthode.Ce que vous avez là est une indication assez sûre que la classe en question viole le principe de responsabilité unique parce qu'elle a trop de dépendances. Recherchez des moyens de refactoriser ces dépendances en clusters de dépendances de façade .
la source
Changez simplement votre structure de données de paramètres de a
class
à astruct
et vous êtes prêt à partir.La méthode obtiendra maintenant sa propre copie de la structure. Les modifications apportées à la variable argument ne peuvent pas être observées par la méthode et les modifications apportées par la méthode à la variable ne peuvent pas être observées par l'appelant. L'isolement est réalisé sans immuabilité.
Avantages:
Les inconvénients:
la source
Que diriez-vous de créer une classe de générateur dans votre classe de données. La classe de données aura tous les setters comme privés et seul le constructeur pourra les définir.
la source
DoSomeActionParameters.Builder
instance peut être utilisée pour créer et configurer exactement uneDoSomeActionParameters
instance. Après avoir appeléBuild()
les modifications ultérieuresBuilder
des propriétés de, la modification des propriétés de l'instance d' origine continueraDoSomeActionParameters
et les appels suivants àBuild()
continueront à renvoyer la mêmeDoSomeActionParameters
instance. Ça devrait vraiment l'êtrepublic DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
.Je ne suis pas un programmeur C # mais je crois que C # prend en charge les arguments nommés: (F # le fait et C # est en grande partie compatible avec ce genre de chose) Il le fait: http://msdn.microsoft.com/en-us/library/dd264739 .aspx # Y342
Ainsi, appeler votre code d'origine devient:
cela ne prend plus de place / pense que votre création d'objet et a tous les avantages, du fait que vous n'avez pas changé du tout ce qui se passe dans le système sous-jacent. Vous n'avez même pas besoin de recoder quoi que ce soit pour indiquer que les arguments sont nommés
Edit: voici un artical que j'ai trouvé à ce sujet. http://www.globalnerdy.com/2009/03/12/default-and-named-parameters-in-c-40-sith-lord-in-training/ Je devrais mentionner que C # 4.0 prend en charge les arguments nommés, 3.0 ne l'a pas fait
la source
Pourquoi ne pas simplement créer une interface qui renforce l'immuabilité (c'est-à-dire uniquement les getters)?
C'est essentiellement votre première solution, mais vous forcez la fonction à utiliser l'interface pour accéder au paramètre.
et la déclaration de fonction devient:
Avantages:
struct
solutionLes inconvénients:
DoSomeActionParameters
s'agit d'une classe qui pourrait être mappéeIDoSomeActionParameters
la source
IDoSomeActionParameters
et veut capturer les valeurs qui y figurent n'a aucun moyen de savoir si la détention de la référence sera suffisante, ou si elle doit copier les valeurs dans un autre objet. Les interfaces lisibles sont utiles dans certains contextes, mais pas pour rendre les choses immuables.Je sais que c'est une vieille question, mais j'ai pensé que j'allais suivre ma suggestion car je devais simplement résoudre le même problème. Maintenant, je reconnais que mon problème était légèrement différent du vôtre car j'avais l'exigence supplémentaire de ne pas vouloir que les utilisateurs puissent construire cet objet eux-mêmes (toute l'hydratation des données provenait de la base de données, donc je pouvais emprisonner toute construction en interne). Cela m'a permis d'utiliser un constructeur privé et le modèle suivant;
la source
Vous pouvez utiliser une approche de style Builder, bien que, selon la complexité de votre
DoSomeAction
méthode, cela puisse être un peu lourd. Quelque chose dans ce sens:la source
En plus de la réponse manji, vous pouvez également diviser une opération en plusieurs opérations plus petites. Comparer:
et
Pour ceux qui ne connaissent pas POSIX la création d'enfant peut être aussi simple que:
Chaque choix de conception représente un compromis sur les opérations qu'il peut effectuer.
PS. Oui - il est similaire au constructeur - uniquement en sens inverse (c'est-à-dire du côté de l'appelé au lieu de l'appelant). Il se peut que ce soit mieux ou non le constructeur dans ce cas précis.
la source
en voici un légèrement différent de Mikeys mais ce que j'essaie de faire, c'est de rendre le tout aussi peu à écrire que possible
Le DoSomeActionParameters est immuable comme il peut l'être et ne peut pas être créé directement car son constructeur par défaut est privé
L'initialiseur n'est pas immuable, mais seulement un transport
L'utilisation tire parti de l'initialiseur sur l'initialiseur (si vous obtenez ma dérive) Et je peux avoir des valeurs par défaut dans le constructeur par défaut de l'initialiseur
Les paramètres seront facultatifs ici, si vous voulez que certains soient requis, vous pouvez les mettre dans le constructeur par défaut de l'initialiseur
Et la validation pourrait aller dans la méthode Create
Alors maintenant, il ressemble
Encore un peu kooki je sais, mais je vais quand même l'essayer
Edit: déplacer la méthode create vers une statique dans l'objet parameters et ajouter un délégué qui passe l'initialiseur enlève une partie de la kookieness de l'appel
Donc l'appel ressemble maintenant à ceci
la source
Utilisez la structure, mais au lieu des champs publics, ayez des propriétés publiques:
Jon et FXCop seront satisfaits car vous exposez des propriétés et non des champs.
Eric sera satisfait car en utilisant les propriétés, vous pouvez vous assurer que la valeur n'est définie qu'une seule fois.
Un objet semi-immuable (la valeur peut être définie mais pas modifiée). Fonctionne pour les types valeur et référence.
la source
this
sont bien plus mauvaises que les champs publics. Je ne sais pas pourquoi vous pensez que le fait de ne régler la valeur qu'une seule fois rend les choses "correctes"; si on commence par une instance par défaut d'une structure, les problèmes associés auxthis
propriétés -mutating continueront d'exister.Une variante de la réponse de Samuel que j'ai utilisée dans mon projet lorsque j'ai eu le même problème:
Et à utiliser:
Dans mon cas, les paramètres étaient intentionnellement modifiables, car les méthodes de setter ne permettaient pas toutes les combinaisons possibles, et exposaient simplement des combinaisons communes d'entre elles. C'est parce que certains de mes paramètres étaient assez complexes et que des méthodes d'écriture pour tous les cas possibles auraient été difficiles et inutiles (les combinaisons folles sont rarement utilisées).
la source
DoMagic
a cependant dans son contrat qu'il ne modifiera pas l'objet. Mais je suppose que cela ne protège pas des modifications accidentelles.