web-dev-qa-db-fra.com

Constructeur de refactorisation qui a trop de paramètres

Je suis dans mes deux premiers mois en tant qu'ingénieur logiciel et je voulais juste obtenir des conseils sur la possibilité d'améliorer cela.

J'ai créé une classe qui représente les données de la RFID sous la forme d'un message:

 class RFIDMessage
 {

        string _a;
        string _b;
        string _c;
        string _d;
        string _e;
        string _f;
        string _g;
        string _h;

        public RFIDMessage(string a, string b, string c, string d, string e, string f, string g, string h)
        {
            _a = a;
            _b = b;
            _c = c;
            _d = d;
            _e = e;
            _f = f;
            _g = g;
            _h = h;
        }
}

J'ai lu sur le principe de responsabilité unique et je ne peux pas déterminer si ce constructeur peut être amélioré en lui fournissant un nouveau type de données. Ce message est analysé à partir des données lues à partir d'une étiquette RFID et avoir huit paramètres semble un peu trop. Les paramètres ne changeront pas dans cette instance mais il existe d'autres types de messages - c'est juste celui par défaut.

Mes questions sont:

1) Existe-t-il un moyen de réduire le nombre de paramètres qui rend le code plus maintenable?

2) Comment puis-je changer ou améliorer la conception de cette classe pour accueillir plus de types de messages?

15
M161

Vous avez raison, 8 paramètres -toutes les chaînes- font du constructeur un bon candidat pour une révision de code.

Considérez certains des points suivants.

Attributs essentiels d'abord

Regardez le modèle de message et déterminez quels attributs sont nécessaires pour initialiser une instance dans un état cohérent. Réduisez le nombre d'arguments à l'essentiel. Ajoutez des paramètres ou des fonctions pour le reste.

Si les 8 attributs sont obligatoires et en lecture seule, il n'y a pas trop à faire.

Encapsulation

Envisagez d'encapsuler des paramètres corrélés. Par exemple, A, B et C peuvent être placés ensemble dans une nouvelle classe. Découvrez quels paramètres changent ensemble, en même temps, pour les mêmes raisons.

Il réduit le nombre d'arguments au prix d'une classe de plus (complexité).

Utilisez modèles de création

Au lieu d'initialiser les messages directement depuis n'importe quel endroit de la source de code, faites-le depuis sines ou constructeurs .

Tableaux

Si aucun des éléments ci-dessus ne fonctionne, essayez un tableau de paramètres. Étant donné le manque de noms de paramètres significatifs, c'est probablement la solution la plus simple.

Concernant les tableaux, j'ai posté une question où je pose des questions sur sa pertinence. J'étais réticent à faire confiance à une telle solution. Cependant, les réponses m'ont aidé à être moins réticent, alors vérifiez si vous n'aimez pas cette solution. Cela pourrait changer d'avis à ce sujet.

Héritage

Finalement, vous vous rendrez compte que les messages sont de bons candidats à l'héritage. La segmentation des messages par attribut génère un peu de surcharge car tôt ou tard vous finissez par demander le type if(message.getType() == ...) partout dans le code.

19
Laiv

Voici une réponse hérétique: je ne vois rien de mal en soi avec beaucoup de paramètres.

Avoir une fonction qui prend beaucoup de paramètres est souvent une cible pour le refactoring, mais ce n'est pas parce que nous avons refactorisé quelque chose dans un morceau de code plus frais que nous devrions l'ont fait.

L'utilisation d'un tableau ou d'arguments de tableau au lieu de 8 paramètres séparés signifie que vous aurez besoin d'une logique supplémentaire pour faire en sorte qu'il y ait le bon nombre de paramètres transmis. L'utilisation d'un modèle de générateur est très bien, sauf que si je viens derrière vous pour maintenir le code et vous avez un modèle de générateur pour chaque petite structure de données stupide, alors je suis susceptible de me perdre dans vos lignes de code bajillion. Si vous avez vraiment besoin de tous les 8 arguments, vous ne pouvez pas en déplacer uniquement pour les accessoires de définition, car vous pourriez avoir une instance de la classe sans toutes les données nécessaires.

Je sais pour ma propre expérience que si j'hérite d'une base de code, je préférerais de beaucoup que le code soit configuré dans un style ennuyeux, fastidieux et évident (comme vos 8 arguments de constructeur, etc.) au lieu d'un peu cool/nouveau cadre/modèle DI complexe que je n'ai jamais utilisé auparavant.

Si votre classe ci-dessus est une structure de données simple (espérons-le immuable) qui a vraiment besoin d'exactement 8 arguments de chaîne pour être valide, alors je dis qu'avoir 8 paramètres est une manière parfaitement fine d'accomplir cela.

32
Graham

Je vais travailler avec l'hypothèse que vous voulez que cette classe soit immuable.

Si vous voulez l'immuabilité "vraie", vous devrez vous en tenir aux arguments du constructeur, car le constructeur est le seul morceau de code qui peut définir un champ readonly.

Cependant, vous pouvez également simplement implémenter des getters publics et des setters privés. À toutes fins utiles, cela rend l'objet immuable aussi. Vous devez définir les champs privés dans une méthode d'usine statique (ish).

Dans cet exemple, je suppose que les données RFID arrivent à votre application dans un flux, mais vous pouvez facilement modifier cela pour fonctionner avec un tableau d'octets, une série de structures, XML, peu importe.

class RFIDMessage
{
    private string _a;
    private string _b;
    private string _c;
    //etc

    private RFIDMessage() 
    {
        //Declare default constructor private so nobody else can instantiate an RFIDMessage on their own
    }

    static public RFIDMessage FromStream(Stream stream)
    {
        var reader = new RFIDStreamReader(stream);
        var newObject = new RFIDMessage();
        newObject._a = reader.ReadA();
        newObject._b = reader.ReadB();
       //etc.

       return newObject;
    }
}

void SampleUsage()
{
    var rfidStream = RFIDAPI.GetStream(); //Or whatever
    RFIDMessage message = RFIDMessage.FromStream(rfidStream);
}

Je ne connais pas le nom de ce modèle, mais il est courant dans le .NET CLR, par exemple FromBase64String .

3
John Wu

Supposons que vous ayez besoin de tous ces bits de données en une seule instance et évitez de remettre en question la conception de la classe. Peut-être que la classe pourrait avoir moins de membres, et la quantité globale de code pourrait être plus petite, si vous créiez une autre classe de couche inférieure collectant un sous-ensemble de ces membres. Ceci n'est pas la question.

Si vous lisez le framework .net directives de conception concernant les constructeurs , vous pouvez voir que de plus petits nombres de paramètres sont préférés, à la fois parce que les valeurs par défaut des membres peuvent réduire le code, et parce qu'il est difficile de passer de nombreux paramètres sans mélanger l'ordre. L'alternative consiste à utiliser des propriétés pour configurer l'instance avant utilisation. Vous définissez les propriétés qui nécessitent des valeurs non par défaut et laissez les autres telles quelles. Cela peut le rendre plus facile à utiliser.

Étant donné que les champs en lecture seule doivent être préférés aux champs mutables, lorsque des champs mutables ne sont pas nécessaires, il y a un compromis. Vous pouvez uniquement initialiser des champs en lecture seule via des constructeurs. Vous n'avez utilisé aucun champ en lecture seule, mais vous souhaiterez peut-être reconsidérer cette décision. C'est une faiblesse de l'environnement qui vous oblige à prendre la décision.

1
Frank Hileman

Bien que je pense que votre code pourrait être correct en fonction de la situation réelle, je voudrais montrer une autre solution possible: "syntaxe fluide". L'idée derrière une syntaxe fluide est de fournir des méthodes pour changer l'état de l'objet qui renvoie l'objet modifié. Par exemple.

public RFIDMessage SetA(string a)
{
    this._a = a;
    return this;
}

Il permet un code comme

RFIDMessage message = new RFIDMessage().SetA("Some A").SetC("Some C").SetH("Some H");

Notez que maintenant l'instance peut être modifiée après la création, ce qui peut être souhaité ou un problème en fonction de vos besoins. Si c'est un problème, vous pouvez créer une "interface en lecture seule" que la classe implémente (et les méthodes SetA mentionnées ci-dessus ne font pas partie de l'interface) de telle sorte que les consommateurs de l'instance sachant que l'interface en lecture seule ne peut pas changes le.

1
Bernhard Hiller