Tout d'abord, je veux expliquer une hypothèse que je fais pour cette réponse. Elle n'est pas toujours vraie, mais assez souvent :
Les interfaces sont des adjectifs ; les classes sont des noms.
(En fait, il existe aussi des interfaces qui sont des noms, mais je veux généraliser ici).
Ainsi, par exemple, une interface peut être quelque chose comme IDisposable
, IEnumerable
ou IPrintable
. Une classe est une implémentation réelle d'une ou plusieurs de ces interfaces : List
ou Map
peuvent tous deux être des implémentations de IEnumerable
.
Pour aller droit au but : Souvent, vos classes dépendent les unes des autres. Par exemple, vous pouvez avoir un Database
qui accède à votre base de données (hah, surprise ! ;-)), mais vous voulez aussi que cette classe fasse de la journalisation sur l'accès à la base de données. Supposons que vous ayez une autre classe Logger
alors Database
a un lien de dépendance avec Logger
.
Jusqu'à présent, tout va bien.
Vous pouvez modéliser cette dépendance dans votre Database
avec la ligne suivante :
var logger = new Logger();
et tout va bien. Tout va bien jusqu'au jour où vous réalisez que vous avez besoin d'un tas de loggers : Parfois, vous voulez vous connecter à la console, parfois au système de fichiers, parfois à l'aide de TCP/IP et d'un serveur d'enregistrement distant, et ainsi de suite...
Et bien sûr, vous le faites PAS vous voulez changer tout votre code (entre-temps vous en avez des milliards) et remplacer toutes les lignes
var logger = new Logger();
par :
var logger = new TcpLogger();
Premièrement, ce n'est pas amusant. Deuxièmement, c'est source d'erreurs. Enfin, c'est un travail stupide et répétitif pour un singe savant. Alors, que faites-vous ?
Il est évident que c'est une assez bonne idée d'introduire une interface ICanLog
(ou similaire) qui est mis en œuvre par tous les différents enregistreurs. Donc l'étape 1 de votre code est que vous faites :
ICanLog logger = new Logger();
Maintenant, l'inférence de type ne change plus de type, vous avez toujours une seule interface à développer. L'étape suivante est que vous ne voulez pas avoir de new Logger()
encore et encore. Vous confiez donc la fiabilité de la création de nouvelles instances à une classe d'usine unique et centrale, et vous obtenez un code tel que :
ICanLog logger = LoggerFactory.Create();
La fabrique elle-même décide du type de logger à créer. Votre code ne s'en soucie plus, et si vous voulez changer le type de logger utilisé, vous le modifiez une fois : A l'intérieur de l'usine.
Maintenant, bien sûr, vous pouvez généraliser cette usine, et la faire fonctionner pour n'importe quel type :
ICanLog logger = TypeFactory.Create<ICanLog>();
Quelque part, cette TypeFactory a besoin de données de configuration pour savoir quelle classe réelle doit être instanciée lorsqu'un type d'interface spécifique est demandé, ce qui nécessite un mappage. Bien sûr, vous pouvez faire ce mappage dans votre code, mais alors un changement de type signifie une recompilation. Mais vous pouvez également placer ce mappage dans un fichier XML, par exemple. Cela vous permet de changer la classe réellement utilisée même après la compilation ( !), c'est-à-dire dynamiquement, sans recompilation !
Pour vous donner un exemple utile à ce sujet : Pensez à un logiciel qui n'enregistre pas normalement, mais lorsque votre client appelle et demande de l'aide parce qu'il a un problème, tout ce que vous lui envoyez est un fichier de configuration XML mis à jour, et maintenant il a l'enregistrement activé, et votre support peut utiliser les fichiers journaux pour aider votre client.
Et maintenant, en remplaçant un peu les noms, on se retrouve avec une implémentation simple d'une Localisateur de services qui est l'un des deux modèles de Inversion du contrôle (puisque vous inversez le contrôle sur qui décide de la classe exacte à instancier).
Dans l'ensemble, cela réduit les dépendances dans votre code, mais maintenant tout votre code a une dépendance envers le localisateur de service central et unique.
Injection de dépendances est maintenant la prochaine étape de cette ligne : Il suffit de se débarrasser de cette dépendance unique au localisateur de services : Au lieu que différentes classes demandent au localisateur de services une implémentation pour une interface spécifique, vous reprenez - une fois de plus - le contrôle de qui instancie quoi.
Avec l'injection de dépendances, votre Database
a maintenant un constructeur qui requiert un paramètre de type ICanLog
:
public Database(ICanLog logger) { ... }
Maintenant votre base de données a toujours un logger à utiliser, mais elle ne sait plus d'où vient ce logger.
Et c'est là qu'un cadre DI entre en jeu : Vous configurez à nouveau vos mappings, puis vous demandez à votre framework DI d'instancier votre application pour vous. Comme le Application
nécessite un ICanPersistData
une instance de Database
est injecté - mais pour cela il doit d'abord créer une instance du type de logger qui est configuré pour ICanLog
. Et ainsi de suite...
Donc, pour faire court : l'injection de dépendances est l'une des deux façons de supprimer les dépendances dans votre code. C'est très utile pour les changements de configuration après la compilation, et c'est une excellente chose pour les tests unitaires (car il est très facile d'injecter des stubs et / ou des mocks).
En pratique, il y a des choses que vous ne pouvez pas faire sans un localisateur de services (par exemple, si vous ne savez pas à l'avance combien d'instances vous avez besoin d'une interface spécifique : Un framework DI n'injecte toujours qu'une seule instance par paramètre, mais vous pouvez appeler un localisateur de services à l'intérieur d'une boucle, bien sûr), c'est pourquoi le plus souvent chaque framework DI fournit également un localisateur de services.
Mais en fait, c'est tout.
P.S. : Ce que j'ai décrit ici est une technique appelée injection de constructeur il y a aussi injection de biens où ce ne sont pas des paramètres de constructeur, mais des propriétés qui sont utilisées pour définir et résoudre les dépendances. Considérez l'injection de propriétés comme une dépendance facultative, et l'injection de constructeurs comme une dépendance obligatoire. Mais la discussion sur ce point dépasse le cadre de cette question.
3 votes
Cela peut aussi être une information utile : martinfowler.com/articles/injection.html
1 votes
stackoverflow.com/questions/1638919/
0 votes
@Steven Oui. En fait, pas dans mon code C#, mais je suis prêt à donner une explication. Et non, je n'ai jamais eu de douleurs ou de problèmes avec ça. Mon code (et celui de mes équipes) est toujours très bien conçu.
1 votes
Voir aussi .
0 votes
J'ai trouvé cette vidéo sur la façon de construire un simple DI Container assez étonnante : (Par le développeur de Funq) blogs.clariusconsulting.net/kzu/
56 votes
Je suis avec ce type : jamesshore.com/Blog/Dependency-Injection-Demystified.html .
0 votes
J'ai également écrit un article à ce sujet : codeproject.com/Articles/386164/
0 votes
La seule raison de son utilisation est le découplage, le découplage, le découplage et le découplage. youtube.com/watch?v=FuAhnqSDe-o
0 votes
J'ai vraiment apprécié cette lecture - joelabrahamsson.com/
5 votes
Une autre explication très simple de DI : codearsenal.net/2015/03/
0 votes
Dans mon cas, j'ai compris la prémisse, le problème qu'elle avait, j'ai eu le problème et cela m'a frappé de plein fouet. J'aurais aimé apprendre cela avant même de commencer mon projet. Je suis du côté d'Android/Java donc j'utilise Dagger2. Maintenant, en creusant plus profondément, c'est juste encore plus de désordre absolu que je dois traiter, je le trouve compliqué (même s'il fait de la publicité pour m'éviter d'écrire du code passe-partout, ce que je crois vraiment) mais la courbe d'apprentissage pour le contourner est extrêmement élevée. Le concept est bon mais l'hélioglyphe supplémentaire à écrire par-dessus mon propre bazar ne vaut pas la peine. Mais j'essaie quand même.