173 votes

Comment éviter le problème du "trop de paramètres" dans la conception d'une API ?

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 est transmis. Il est plus difficile de refactoriser une méthode en parties plus petites parce que cela crée un autre surcoût de passer tous les paramètres dans les sous-fonctions. Le code est plus difficile à lire.

J'ai trouvé 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 d'API à :

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

Joli. Cela semble très innocent, mais nous avons en fait introduit un énorme changement : nous avons introduit la mutabilité. Car ce que nous faisions auparavant était en fait de passer un objet anonyme immuable : les paramètres de la fonction sur la pile. Maintenant nous avons créé une nouvelle classe qui est très mutable. Nous avons créé la possibilité de manipuler l'état de la classe appelant . Ça craint. Maintenant je veux que mon objet soit immuable, qu'est-ce que je fais ?

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 en fait recréé mon problème initial : trop de paramètres. Il est évident que ce n'est pas la bonne solution. Que vais-je faire ? La dernière option pour obtenir une telle immuabilité est d'utiliser une structure "readonly" 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, cela résout tous les problèmes (ordre des paramètres, etc.). Pourtant :

C'est alors que j'ai été troublé et que j'ai décidé d'écrire cette question : Quel est le moyen le plus simple en C# d'éviter le problème du "trop de paramètres" sans introduire la mutabilité ? Est-il possible d'utiliser une structure readonly à cette fin sans que l'API soit mal conçue ?

CLARIFICATIONS :

  • Veuillez supposer qu'il n'y a pas de violation du principe de responsabilité unique. Dans mon cas original, la fonction écrit simplement les paramètres donnés dans un seul enregistrement de la base de données.
  • Je ne cherche pas une solution spécifique à la fonction donnée. Je cherche une approche généralisée de ces problèmes. Je suis spécifiquement intéressé par la résolution du problème du "trop grand nombre de paramètres" sans introduire la mutabilité ou une conception terrible.

UPDATE

Les réponses fournies ici présentent différents avantages/inconvénients. C'est pourquoi j'aimerais convertir ce site en un wiki communautaire. Je pense que chaque réponse avec un exemple de code et les avantages et inconvénients ferait un bon guide pour des problèmes similaires à l'avenir. J'essaie maintenant de trouver comment le faire.

92voto

Samuel Neff Points 35554

Utiliser une combinaison d'un constructeur et d'une API de type langage spécifique au domaine - Interface fluente. L'API est un peu plus verbeuse mais avec intellisense, elle est très rapide à taper et facile à comprendre.

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }

  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }

  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

26voto

Teoman Soygul Points 17544

L'un des styles adoptés dans les cadres de travail consiste généralement à regrouper des paramètres apparentés dans des classes apparentées (mais cela pose à nouveau des problèmes de mutabilité) :

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

12voto

Jeffrey L Whitledge Points 27574

Il suffit de modifier la structure de données de votre paramètre, qui passe d'un class à un struct et vous êtes prêt à partir.

public struct DoSomeActionParameters 
{
   public string A;
   public string B;
   public DateTime C;
   public OtherEnum D;
   public string E;
   public string F;
}

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

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'isolation est obtenue sans immutabilité.

Pour :

  • Plus facile à mettre en œuvre
  • Le moindre changement de comportement dans la mécanique sous-jacente

Cons :

  • L'immuabilité n'est pas évidente, elle nécessite l'attention du développeur.
  • Copie inutile pour maintenir l'immuabilité.
  • Occupe l'espace de la pile

11voto

Mark Seemann Points 102767

Ce que vous avez là est une indication assez sûre que la classe en question viole le Principe de responsabilité unique car il a trop de dépendances. Cherchez des moyens de refactoriser ces dépendances en groupes de Dépendances de la façade .

6voto

marto Points 2953

Pourquoi ne pas créer une classe de construction à l'intérieur de votre classe de données ? La classe de données aura tous les setters comme privés et seul le builder sera capable de les définir.

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 class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

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