Réduire le nombre d’arguments à un constructeur

Je lis “Clean Code” et j’ai du mal à comprendre comment garder certaines de mes fonctions (généralement des constructeurs) à leur MAXIMUM de 3 parameters.

Souvent, mes objects ont besoin de beaucoup d’informations pour fonctionner – est-ce que je suis censé créer un petit constructeur, puis utiliser des fonctions de mutateur pour leur donner toutes les informations? Cela ne semble pas meilleur que d’utiliser un grand constructeur.

Par exemple, j’ai une classe “MovablePatch”. Il permet à l’utilisateur de faire glisser un carré dans une fenêtre. Il nécessite plusieurs parameters, notamment Radius, Color, Renderer, InitialPosition et Visibility. Actuellement, je recueille tous ces éléments à partir de mon interface graphique, puis appelle:

MovablePatch(int radius, Renderer* renderer, Color color, Position initial, bool visibility) 

Ce ne sont que quelques-unes des choses dont j’ai besoin dans cette classe. Quelqu’un peut-il suggérer comment je pourrais empaqueter ces informations pour les transmettre au constructeur? Je ne vois aucune évidence “casser en classes plus petites” apparaissant ici.

Tu aurais pu

 MovablePatch(Renderer* renderer, CircleAppearance circleAppearance) 

où CircleAppearance rassemble les autres informations.

Cependant, le code propre et les autres livres qui donnent une idée générale de ce que devrait être un bon code visent 80% du code. Votre code semble être “plus proche du métal” que la variété typique de lignes de paiement. En tant que tel, vous pouvez rencontrer des endroits où certains idéaux de codage ne sont pas applicables.

Le plus important, c’est que vous y réfléchissiez et que vous essayiez de garder les choses bien rangées! 🙂

Ne prenez pas de maximes comme “tu n’auras pas plus de 3 parameters dans tes constructeurs” à leur valeur nominale. Si vous avez la moindre chance de rendre un object immuable, faites-le; et si cela est immuable, cela signifie qu’il aura un constructeur avec 50 parameters, ainsi soit-il; fonce; n’y pense même pas deux fois.

Même si l’object va être mutable, vous devez quand même transmettre son constructeur autant de parameters que nécessaire pour que, dès sa construction, il soit dans un état valide et significatif. Dans mon livre, il est absolument inadmissible de devoir savoir quelles sont les méthodes de mutations magiques qui doivent être appelées (parfois même dans le bon ordre) avant d’invoquer d’autres méthodes, sous peine de segfault.

Cela dit, si vous souhaitez vraiment réduire le nombre de parameters à un constructeur, ou à une fonction quelconque, passez simplement cette méthode à une interface qu’elle peut appeler pour obtenir les éléments dont elle a besoin pour fonctionner.

Certaines des choses que vous transmettez pourraient être extraites dans une construction plus large. Par exemple, la visibility , la color et le radius peuvent avoir un sens pour être placés dans un object que vous définissez. Ensuite, une instance de cette classe, appelée ColoredCircle , pourrait être transmise au constructeur de MovablePatch . Un ColourCircle ne se soucie pas de savoir où il se trouve ou quel moteur de rendu il utilise, mais un MovablePatch le fait.

Mon point principal est que du sharepoint vue OO, le radius n’est pas vraiment un entier, c’est un rayon. Vous voulez éviter ces longues listes de constructeurs car il est décourageant de comprendre le contexte de ces choses. Si vous les collectez dans une classe plus grande, un peu comme vous l’avez déjà fait avec Color et Position , vous pouvez avoir moins de parameters passés et les rendre plus faciles à comprendre.

L’ idiome du paramètre nommé est utile ici. Dans votre cas, vous pourriez avoir

 class PatchBuilder { public: PatchBuilder() { } PatchBuilder& radius(int r) { _radius = r; return *this; } PatchBuilder& renderer(Renderer* r) { _renderer = r; return *this; } PatchBuilder& color(const Color& c) { _color = c; return *this; } PatchBuilder& initial(const Position& p) { _position = p; return *this; } PatchBuilder& visibility(bool v) { _visibility = v; return *this; } private: friend class MovablePatch; int _radius; Renderer* _renderer; Color _color; Position _position; bool _visibility; }; class MovablePatch { public: MovablePatch( const PatchBuilder& b ) : _radius( b._radius ); _renderer( b._renderer ); _color( b._color ); _position( b._position ); _visibility( b._visibility ); { } private: int _radius; Renderer* _renderer; Color _color; Position _position; bool _visibility; }; 

alors vous l’utilisez comme ça

 int main() { MovablePatch foo = PatchBuilder(). radius( 1.3 ). renderer( asdf ). color( asdf ). position( asdf ). visibility( true ) ; } 

trop simplifié, mais je pense que cela fait la différence. Si certains parameters sont requirejs, ils peuvent être inclus dans le constructeur PatchBuilder :

 class PatchBuilder { public: PatchBuilder(const Foo& required) : _foo(required) { } ... }; 

Évidemment, ce modèle dégénère en problème d’origine si tous les arguments sont requirejs, auquel cas le paramètre nommé idiom n’est pas applicable. Le point étant, ce n’est pas une solution unique, et comme Adam décrit dans le commentaire ci-dessous, il y a des coûts supplémentaires et des frais généraux pour le faire.

Une bonne option consiste à utiliser un modèle de générateur, où chaque méthode “setter” renvoie la propre instance et vous pouvez enchaîner les méthodes selon vos besoins.

Dans votre cas, vous obtiendrez une nouvelle classe MovablePatchBuilder .

L’approche est très utile et vous pouvez la trouver dans de nombreux frameworks et langages différents.

Reportez-vous ici pour voir quelques exemples.