Refactoriser les grands constructeurs

Nous avons quelques objects dans notre modèle de domaine avec ce que vous appelleriez comiquement des constructeurs offensivement grands, si grands qu’IntelliSense renonce à essayer de tout vous montrer …

Cue un type avec environ 50 arguments, principalement des types de valeur, quelques types de référence:

public class MyLegacyType { public MyLegacyType(int a1, int a2, int a3, ... int a50) // etc { } } 

Je vais le dire maintenant, non, ce type ne peut pas changer. Le type lui-même représente logiquement une entité qui se trouve être lourde de propriétés. Les appelants qui construisent ce type fournissent la majorité des arguments de plusieurs sources, bien que certains soient définis par défaut. Peut-être existe-t-il un modèle pour que les sources soient fournies à la construction au lieu des résultats.

Cependant, ce qui peut changer est la manière dont le type est créé. Nous avons actuellement des sections de code qui souffrent de:

  • Manque d’IntelliSense sur le type.
  • Code laid et illisible.
  • Douleur de fusion due à Connascence of Position .

Une solution immédiate consiste à utiliser des parameters facultatifs pour les valeurs par défaut et des arguments nommés pour faciliter la fusion. Nous faisons cela dans une certaine mesure sur d’autres types, cela fonctionne bien.

Cependant, on a l’impression que c’est à mi-chemin de la refactorisation complète.

L’autre solution évidente consiste à réduire les parameters de constructeur avec des types de conteneur dotés de propriétés pour ce qui était auparavant des arguments de constructeur. Cela permet de bien ranger les constructeurs et vous permet d’incorporer des valeurs par défaut dans les conteneurs, mais déplace essentiellement le problème sur un autre type et équivaut éventuellement à l’utilisation de parameters optionnels / nommés.

Il existe également le concept de constructeurs Fluent … à la fois par propriété ( WithIntA , WithIntB ) ou par type de conteneur ( WithTheseInts(IntContainer c) ). Personnellement, j’apprécie cette approche du côté des appels, mais là encore, sur un grand type, cela devient verbeux et donne l’impression que je viens de déplacer un problème au lieu de le résoudre.

Ma question, s’il en est une qui soit enterrée dans ce gâchis, est la suivante: ces tactiques de refactoring sont-elles viables pour le problème? Veuillez préciser votre réponse avec une expérience, des pièges ou des critiques pertinentes. Je me penche vers le sujet Fluent, parce que je pense que cela a l’air cool et qu’il est assez lisible et facile à fusionner.

Il me manque le Saint Graal des restructurations de constructeurs – je suis donc ouvert aux suggestions. Bien sûr, cela pourrait aussi simplement être un effet secondaire malheureux et inévitable d’avoir un type avec autant de propriétés en premier lieu …

Évidemment, nous n’avons pas beaucoup de contexte ici, mais à plus de 50 parameters, mon interprétation est que cette classe en fait trop et qu’elle est trop complexe. Je commencerais par rechercher des moyens de diviser des fragments en types plus simples et plus ciblés, puis d’ encapsuler des instances de chacun de ces concepts dans la classe composite. Alors ça devient:

 public MyLegacyType(SomeConcept foo, AnotherConcept bar, ...) { } 

où seule la logique nécessaire pour orchestrer les concepts rest dans MyLegacyType (toute logique SomeConcept à SomeConcept y va, etc.).

Cela diffère de vos “parameters de constructeur réduits avec des types de conteneur ayant des propriétés pour ce qui était auparavant des arguments de constructeur”, car nous restructurons fondamentalement la logique – pas simplement en utilisant un object pour remplacer les arguments de constructeur.

Je voudrais aller avec les types de conteneur et utiliser l’affectation immédiate des propriétés de C # 4.0. De cette façon, on pourrait facilement utiliser Intellisense sur le type résultant, tout en conservant un découplage décent du type initial.

Par exemple:

 public class MyLegacyType { public MyLegacyType(MyConfiguration configuration) // etc { // ... } } public class MyConfiguration { public int Value1 { get; set; } public int Value2 { get; set; } // ... } 

Et alors:

 var myInstance = new MyLegacyType(new MyConfiguration { Value1 = 123, Value2 = 456 }); 

Il y a une chose que je ne suis pas sûre de votre question, et c’est pourquoi voulez-vous que tous ces parameters soient présents dans le constructeur? Utilisez-vous tous les parameters du code constructeur? Votre problème avec intellisense provient probablement du fait qu’il ya trop de parameters dans une seule méthode. Avoir un grand nombre de champs / propriétés sur un même type ne posera aucun problème.

Il semble que vous ayez trouvé des moyens de gérer le nombre d’arguments, mais si vous pouvez expliquer pourquoi vous devez tous les recevoir dans un constructeur, nous pouvons sortir de cette zone. Il y a peut-être quelque chose à regarder.