Éviter les constructeurs avec de nombreux arguments

10

J'ai donc une usine qui crée des objets de différentes classes. Les classes possibles sont toutes dérivées d'un ancêtre abstrait. La fabrique a un fichier de configuration (syntaxe JSON) et décide quelle classe créer, selon la configuration de l'utilisateur.

Pour ce faire, la fabrique utilise boost :: property_tree pour l'analyse JSON. Il parcourt le ptree et décide quel objet concret créer.

Cependant, les objets-produits ont de nombreux champs (attributs). Selon la classe concrète, l'objet a environ 5 à 10 attributs, à l'avenir peut-être encore plus.

Je ne sais donc pas à quoi devrait ressembler le constructeur des objets. Je peux penser à deux solutions:

1) Le constructeur du produit attend chaque attribut comme paramètre, ainsi, le constructeur se retrouvera avec 10+ paramètres. Ce sera laid et conduira à de longues lignes de code illisibles. Cependant, l'avantage est que la fabrique peut analyser le JSON et appeler le constructeur avec les paramètres corrects. La classe de produit n'a pas besoin de savoir qu'elle a été créée en raison de la configuration JSON. Il n'a pas besoin de savoir qu'il y a du JSON ou une configuration impliquée du tout.

2) Le constructeur du produit n'attend qu'un seul argument, l'objet property_tree. Ensuite, il peut analyser les informations nécessaires. Si des informations dans la configuration sont manquantes ou hors limites, chaque classe de produit peut réagir correctement. L'usine n'a pas besoin de connaître les arguments nécessaires aux différents produits. L'usine n'a pas non plus besoin de savoir comment réagir en cas de mauvaise configuration. Et l'interface constructeur est unifiée et petite. Mais, comme inconvénient, le produit doit extraire les informations nécessaires du JSON, ainsi, il sait comment il est construit.

J'ai tendance à préférer la solution 2). Cependant, je ne suis pas sûr que ce soit un bon modèle d'usine. Il semble en quelque sorte mal de laisser le produit savoir qu'il est créé avec la configuration JSON. D'un autre côté, de nouveaux produits peuvent être introduits très simplement.

Des opinions à ce sujet?

lugge86
la source
1
J'ai suivi ton lien. Il y a un exemple dans la réponse la mieux notée de freaket freak. Mais quel problème cette approche "constructeur" résout-elle? Il y a la ligne de code DataClass data = builder.createResult () ;. Mais la méthode createResults () doit encore obtenir les 10 paramètres dans l'objet DataClass. Mais comment? Il semble que vous ayez juste une couche d'abstraction de plus, mais le constructeur de DataClass ne diminue pas.
lugge86
Jetez un œil au constructeur et au prototype.
Silviu Burcea
Silviu Burcea, je l'ai fait. Cependant, lors de l'utilisation du générateur, comment le générateur obtient-il les paramètres dans le produit? Quelque part, il doit y avoir une grosse interface. Le constructeur n'est qu'une couche de plus, mais d'une manière ou d'une autre les paramètres doivent trouver leur chemin dans la classe de produits.
lugge86
1
Si votre classe est trop grande, se déplacer autour des arguments du constructeur ne la rendra pas trop grande .
Telastyn

Réponses:

10

Je ne ferais pas l'option 2, car alors vous avez pour toujours compliqué la construction de votre objet avec l'analyse syntaxique de l'arbre des propriétés. Si vous êtes à l'aise avec une classe qui a besoin d'autant de paramètres, vous devriez être à l'aise avec un constructeur qui a besoin d'autant de paramètres, c'est la vie!

Si votre principale préoccupation est la lisibilité du code, vous pouvez utiliser le modèle de générateur, il s'agit essentiellement du stopgap c ++ / java par manque d'arguments nommés. Vous vous retrouvez avec des choses qui ressemblent à ceci:

MyObject o = MyObject::Builder()
               .setParam1(val1)
               .setParam2(val2)
               .setParam3(val3)
             .build();

Alors maintenant, MyObject aura un constructeur privé, qui sera appelé dans Builder :: build. La bonne chose est que ce sera le seul endroit où vous aurez jamais à appeler un constructeur avec 10 paramètres. La fabrique d'arborescence de propriétés boost utilisera le générateur, et par la suite si vous voulez construire un MyObject directement ou à partir d'une source différente, vous passerez par le générateur. Et le générateur vous permet de nommer clairement chaque paramètre au fur et à mesure que vous le transmettez, afin qu'il soit plus lisible. Cela ajoute évidemment du passe-partout, vous devrez donc décider si cela en vaut la peine par rapport à simplement appeler le constructeur en désordre, ou regrouper certains de vos paramètres existants dans des structures, etc. Il suffit de lancer une autre option sur la table.

https://en.wikipedia.org/wiki/Builder_pattern#C.2B.2B_Example

Nir Friedman
la source
5

N'utilisez PAS la deuxième approche.

Ce n'est certainement pas la solution et ne ferait qu'instancier des cours dans votre logique métier, au lieu de la partie de votre application où se trouvent les usines.

Soit:

  • essayez de regrouper certains paramètres qui semblent représenter des choses similaires en objets
  • diviser la classe actuelle en plusieurs classes plus petites (avoir une classe de service avec 10 paramètres semble que la classe fasse trop de choses)
  • laissez-le tel quel, si votre classe n'est pas réellement un service, mais un objet de valeur à la place

À moins que l'objet que vous créez n'est en fait une classe qui est responsable de la conservation des données, vous devez essayer de refactoriser le code et de diviser la grande classe en plus petites.

Andy
la source
Eh bien, la logique bsiness utilise une usine qui retourne les produits, donc la logique métier ne voit pas le truc JSON / ptree. Mais je vois votre point de vue, avoir du code Parser dans la construction se sent mal.
lugge86
La classe représente un widget dans une interface graphique pour un système embarqué, ainsi, plus de 5 attributs me semblent OK: x_coord, y_coord, backgroundcolor, framesize, framecolor, text ...
lugge86
1
@ lugge86 Même si vous utilisez une fabrique pour analyser le JSON et évitez ainsi d'appeler newou de construire des objets dans votre logique métier, ce n'est pas une très bonne conception. Consultez le discours Ne cherchez pas les choses de Miško Hevery , qui explique plus en détail pourquoi l'approche d'usine que vous avez suggérée est mauvaise du point de vue des tests et de la lecture. De plus, votre classe semble être un objet de données, et pour ceux-ci, il est généralement correct d'avoir plus de paramètres que la classe de service standard. Je ne serais pas trop gêné.
Andy
Je me sens bien avec mon approche d'usine, mais je vais suivre votre lien et y réfléchir. Cependant, l'usine n'est pas en cause dans ce sujet. La question est toujours de savoir comment configurer les produits ...
lugge86
"avoir une classe de service avec 10 paramètres semble que la classe fasse trop de choses" Pas dans l'apprentissage automatique. Tout algorithme ML aurait des tonnes de paramètres réglables. Je me demande quelle est la bonne façon de gérer cela lors du codage du ML.
Siyuan Ren
0

L'option 2 a presque raison.

Une option améliorée 2

Créez une classe "orientée vers l'avant" à qui il revient de prendre cet objet de structure JSON et de sélectionner les bits et d'appeler le ou les constructeurs d'usine. Il prend ce que fabrique l'usine et le donne au client.

  • L'usine n'a absolument aucune idée qu'un tel truc JSON existe même.
  • Le client n'a pas besoin de savoir de quels bits spécifiques l'usine a besoin.

Fondamentalement, le "front end" dit aux 2 Bobs: "Je traite avec les clients expurgés afin que les ingénieurs n'aient pas à le faire! J'ai des compétences en relations humaines!" Pauvre Tom. S'il avait seulement dit "je dissocie le client de la construction. Ce résultat est une usine très cohésive"; il aurait pu garder son emploi.

Trop d'arguments?

Pas pour le client - communication front-end.

Front end - usine? Si ce n'est pas 10 paramètres, le mieux que vous puissiez faire est de différer le déballage, sinon la chose JSON d'origine, puis un DTO. Est-ce mieux que de passer le JSON à l'usine? Même différence que je dis.

J'envisagerais fortement de passer des paramètres individuels. Tenez-vous à l'objectif d'une usine propre et cohérente. Évitez les préoccupations de la réponse @DavidPacker.

Atténuer «trop d'arguments»

  • Constructeurs d'usine ou de classe

    • en prenant uniquement des arguments pour une construction de classe / objet spécifique.
    • paramètres par défaut
    • paramètres facultatifs
    • arguments nommés
  • Regroupement d'arguments frontaux

    • Examine, évalue, valide, définit, etc. les valeurs d'argument guidées par les signatures de constructeur ci-dessus.
radarbob
la source
"L'usine n'a absolument aucune idée qu'un tel truc JSON existe même" - alors, à quoi sert l'usine ?? Il cache les détails de la création du produit à la fois au produit et au consommateur. Pourquoi une autre classe devrait-elle aider? Je vais bien avec l'usine parlant JSON. On peut implémenter une autre usine pour analyser XML et implémenter "Abstract Factory" dans le futur ...
lugge86
M. Factory: "Quel objet voulez-vous? ... Qu'est-ce que c'est? Dites-moi simplement quel objet de classe construire." Le fichier de configuration JSON est une source de données, comme le dit l'oncle Bob "c'est un détail d'implémentation". Il pourrait provenir d'une autre source et / ou sous une autre forme. En général, nous voulons découpler des détails spécifiques de la source de données. Si la source ou le formulaire change, l'usine ne changera pas. Étant donné une source + analyseur, et une usine en tant que modules découplés, les deux sont réutilisables.
radarbob