Je suis le plus souvent tenté d'utiliser "l'injection bâtarde" dans quelques cas. Quand j'ai un constructeur "correct" d'injection de dépendance:
public class ThingMaker {
...
public ThingMaker(IThingSource source){
_source = source;
}
Mais alors, pour les classes que j'ai l'intention de API publiques (classes que d'autres équipes de développement consommeront), je ne peux jamais trouver une meilleure option que d'écrire un constructeur "bâtard" par défaut avec le plus probable nécessaire dépendance:
public ThingMaker() : this(new DefaultThingSource()) {}
...
}
L'inconvénient évident ici est que cela crée une dépendance statique sur DefaultThingSource; dans l'idéal, il n'y aurait pas une telle dépendance, et le consommateur injecterait toujours ce IThingSource qu'il voulait. Cependant, c'est trop difficile à utiliser; les consommateurs veulent renouveler un ThingMaker et se mettre au travail pour créer des choses, puis des mois plus tard, injecter autre chose lorsque le besoin s'en fait sentir. Cela ne laisse que quelques options à mon avis:
Boy, # 3 semble sûr attrayant. Y a-t-il une autre meilleure option? # 1 ou # 2 ne semblent pas en valoir la peine.
Pour autant que je comprends, cette question concerne la façon d'exposer une API faiblement couplée avec des valeurs par défaut appropriées. Dans ce cas, vous pouvez avoir une bonne valeur par défaut locale , auquel cas la dépendance peut être considérée comme facultative. Une façon de gérer les dépendances facultatives consiste à utiliser Injection de propriété au lieu de Injection de constructeur - en fait, c'est en quelque sorte le scénario de l'affiche pour l'injection de propriété.
Cependant, le véritable danger de l'injection bâtarde est lorsque la valeur par défaut est une valeur par défaut étrangère , car cela signifierait que le constructeur par défaut traîne le long d'un couplage indésirable avec le Assemblage implémentant la valeur par défaut. Si je comprends bien cette question, cependant, le défaut prévu proviendrait de la même Assemblée, auquel cas je ne vois aucun danger particulier.
Dans tous les cas, vous pourriez également envisager une façade comme décrit dans l'une de mes réponses précédentes: bibliothèque "conviviale" de Dependency Inject (DI)
BTW, la terminologie utilisée ici est basée sur le langage de modèle de mon livre .
Mon compromis est un tour sur @BrokenGlass:
1) Le constructeur unique est un constructeur paramétré
2) Utilisez la méthode d'usine pour créer un ThingMaker et passez cette source par défaut.
public class ThingMaker {
public ThingMaker(IThingSource source){
_source = source;
}
public static ThingMaker CreateDefault() {
return new ThingMaker(new DefaultThingSource());
}
}
Évidemment, cela n'élimine pas votre dépendance, mais cela me rend plus clair que cet objet a des dépendances dans lesquelles un appelant peut plonger profondément s'il le souhaite. Vous pouvez rendre cette méthode d'usine encore plus explicite si vous le souhaitez (CreateThingMakerWithDefaultThingSource) si cela aide à la compréhension. Je préfère cela à la priorité sur la méthode d'usine IThingSource car elle continue de favoriser la composition. Vous pouvez également ajouter une nouvelle méthode d'usine lorsque le DefaultThingSource est obsolète et avoir un moyen clair de trouver tout le code à l'aide du DefaultThingSource et de le marquer pour être mis à niveau.
Vous avez couvert les possibilités dans votre question. Classe d'usine ailleurs pour plus de commodité ou de commodité au sein de la classe elle-même. La seule autre option peu attrayante serait basée sur la réflexion, cachant encore plus la dépendance.
Une alternative consiste à avoir une méthode d'usineCreateThingSource()
dans votre classe ThingMaker
qui crée la dépendance pour vous.
Pour les tests ou si vous avez besoin d'un autre type de IThingSource
, vous devrez alors créer une sous-classe de ThingMaker
et remplacer CreateThingSource()
pour renvoyer le type concret que vous souhaitez. Évidemment, cette approche n'en vaut la peine que si vous devez principalement être en mesure d'injecter la dépendance pour les tests, mais pour la plupart/toutes les autres utilisations, vous n'avez pas besoin d'un autre IThingSource
Si vous devez avoir une dépendance "par défaut", également connue sous le nom d'Injection de dépendance du pauvre, alors vous devez initialiser et "câbler" la dépendance quelque part.
Je garderai les deux constructeurs mais aurai une usine juste pour l'initialisation.
public class ThingMaker
{
private IThingSource _source;
public ThingMaker(IThingSource source)
{
_source = source;
}
public ThingMaker() : this(ThingFactory.Current.CreateThingSource())
{
}
}
Maintenant, en usine, créez l'instance par défaut et autorisez le remplacement de la méthode:
public class ThingFactory
{
public virtual IThingSource CreateThingSource()
{
return new DefaultThingSource();
}
}
Mise à jour:
Pourquoi utiliser deux constructeurs: Deux constructeurs montrent clairement comment la classe est destinée à être utilisée. Le constructeur sans paramètre déclare: créez simplement une instance et la classe exécutera toutes ses responsabilités. Maintenant, le deuxième constructeur indique que la classe dépend de IThingSource et fournit un moyen d'utiliser une implémentation différente de celle par défaut.
Pourquoi utiliser une usine: 1- Discipline: La création de nouvelles instances ne devrait pas faire partie des responsabilités de cette classe, une classe d'usine est plus appropriée. 2- DRY: Imaginez que dans la même API, d'autres classes dépendent également de IThingSource et font de même. Remplacez une fois que la méthode d'usine renvoyant IThingSource et toutes les classes de votre API commencent automatiquement à utiliser la nouvelle instance.
Je ne vois aucun problème à coupler ThingMaker à une implémentation par défaut d'IThingSource tant que cette implémentation a du sens pour l'API dans son ensemble et vous fournissez également des moyens de remplacer cette dépendance à des fins de test et d'extension.
Je vote pour # 3. Vous vous faciliterez la vie - et celle d'autres développeurs -.
Les exemples généralement liés à ce style d'injection sont souvent extrêmement simplistes: "dans le constructeur par défaut de la classe B
, appelez un constructeur surchargé avec new A()
et soyez sur votre chemin!"
La réalité est que les dépendances sont souvent extrêmement complexes à construire. Par exemple, que faire si B
a besoin d'une dépendance non-classe comme une connexion à une base de données ou un paramètre d'application? Vous liez ensuite la classe B
à la System.Configuration
espace de noms, augmentant sa complexité et son couplage tout en diminuant sa cohérence, le tout pour encoder des détails qui pourraient simplement être externalisés en omettant le constructeur par défaut.
Ce style d'injection indique au lecteur que vous avez reconnu les avantages de la conception découplée mais que vous ne souhaitez pas vous y engager. Nous savons tous que lorsque quelqu'un verra ce constructeur par défaut juteux, facile et à faible friction, il l'appellera, quelle que soit la rigidité de son programme à partir de ce moment. Ils ne peuvent pas comprendre la structure de leur programme sans lire le code source de ce constructeur par défaut, ce qui n'est pas une option lorsque vous distribuez simplement les assemblys. Vous pouvez documenter les conventions de nom de chaîne de connexion et de clé de paramètres d'application, mais à ce stade, le code ne se suffit pas à lui-même et il incombe au développeur de rechercher la bonne incantation.
L'optimisation du code pour que ceux qui l'écrivent puissent s'en tirer sans comprendre ce qu'ils disent est une chanson de sirène, un anti-modèle qui conduit finalement à perdre plus de temps à démêler la magie que de gagner du temps dans l'effort initial. Soit découpler ou pas; garder un pied dans chaque motif diminue la concentration des deux.
Vous n'êtes pas satisfait de l'impureté OO de cette dépendance, mais vous ne dites pas vraiment quel problème cela cause finalement.
Je pense que le plus gros problème ici est le choix du nom, pas l'opportunité d'utiliser la technique.
Pour ce que ça vaut, tout le code standard que j'ai vu dans Java le fait comme ceci:
public class ThingMaker {
private IThingSource iThingSource;
public ThingMaker() {
iThingSource = createIThingSource();
}
public virtual IThingSource createIThingSource() {
return new DefaultThingSource();
}
}
Quiconque ne veut pas d'un objet DefaultThingSource
peut remplacer createIThingSource
. (Si possible, l'appel à createIThingSource
serait ailleurs que par le constructeur.) C # n'encourage pas à remplacer comme Java le fait, et il pourrait ne pas être aussi évident qu'il le ferait être dans Java que les utilisateurs peuvent et peut-être devraient fournir leur propre implémentation IThingSource. (Ni aussi évident comment pour le fournir.) Je suppose que # 3 est le chemin à parcourir, mais je pensais que je voudrais mentionner cela.
Juste une idée - peut-être un peu plus élégante mais malheureusement ne se débarrasse pas de la dépendance:
Avoir une fabrique interne (interne à votre bibliothèque) qui mappe DefaultThingSource à IThingSource, qui est appelée à partir du constructeur par défaut.
Cela vous permet de "renouveler" la classe ThingMaker sans paramètres ni aucune connaissance d'IThingSource et sans dépendance directe sur DefaultThingSource.
Je supporte l'option # 1, avec une extension: faire de DefaultThingSource
une classe publique. Votre formulation ci-dessus implique que DefaultThingSource
sera caché aux consommateurs publics de l'API, mais si je comprends bien votre situation, il n'y a aucune raison de ne pas exposer la valeur par défaut. De plus, vous pouvez facilement documenter le fait qu'en dehors de circonstances spéciales, une new DefaultThingSource()
peut toujours être passée à ThingMaker
.
Pour les API vraiment publiques, je gère généralement cela en utilisant une approche en deux parties:
La partie vraiment délicate ici est de décider quand activer le conteneur # 2, et la meilleure approche de choix dépendra fortement de vos consommateurs d'API.