Tout d'abord, je veux expliquer une hypothèse que je fais pour cette réponse. Ce n'est pas toujours vrai, mais assez souvent:
Les interfaces sont des adjectifs; les classes sont des noms.
(En fait, il y a 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
peut toutes deux être des implémentations de IEnumerable
.
Pour comprendre: souvent, vos classes dépendent les unes des autres. Par exemple, vous pourriez avoir une Database
classe qui accède à votre base de données (hah, surprise! ;-)), mais vous voulez également que cette classe fasse la journalisation sur l'accès à la base de données. Supposons que vous ayez une autre classe Logger
, puis Database
une dépendance à Logger
.
Jusqu'ici tout va bien.
Vous pouvez modéliser cette dépendance à l'intérieur de votre Database
classe avec la ligne suivante:
var logger = new Logger();
et tout va bien. C'est bien jusqu'au jour où vous réalisez que vous avez besoin d'un tas d'enregistreurs: Parfois, vous voulez vous connecter à la console, parfois au système de fichiers, parfois en utilisant TCP / IP et un serveur de journalisation à distance, et ainsi de suite ...
Et bien sûr, vous ne voulez PAS changer tout votre code (en attendant vous en avez des milliers) et remplacer toutes les lignes
var logger = new Logger();
par:
var logger = new TcpLogger();
Tout d'abord, ce n'est pas amusant. Deuxièmement, cela est sujet aux erreurs. Troisièmement, c'est un travail stupide et répétitif pour un singe entraîné. Donc que fais-tu?
Évidemment, c'est une assez bonne idée d'introduire une interface ICanLog
(ou similaire) qui est implémentée par tous les différents enregistreurs. L'étape 1 de votre code consiste donc à:
ICanLog logger = new Logger();
Maintenant que l'inférence de type ne change plus de type, vous avez toujours une seule interface pour vous développer. L'étape suivante est que vous ne voulez pas en avoir new Logger()
encore et encore. Ainsi, vous mettez la fiabilité pour créer de nouvelles instances dans une seule classe d'usine centrale et vous obtenez du code tel que:
ICanLog logger = LoggerFactory.Create();
L'usine elle-même décide du type d'enregistreur à créer. Votre code ne se soucie plus, et si vous voulez changer le type d'enregistreur utilisé, vous le changez une fois : à l'intérieur de l'usine.
Maintenant, bien sûr, vous pouvez généraliser cette usine et la faire fonctionner pour tout type:
ICanLog logger = TypeFactory.Create<ICanLog>();
Quelque part, ce TypeFactory a besoin de données de configuration dont la classe réelle à instancier lorsqu'un type d'interface spécifique est demandé, vous avez donc besoin d'un mappage. Bien sûr, vous pouvez faire ce mappage dans votre code, mais un changement de type signifie alors 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 le temps de compilation (!), C'est-à-dire dynamiquement, sans recompilation!
Pour vous donner un exemple utile: pensez à un logiciel qui ne se connecte 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 la journalisation est activée et votre support peut utiliser les fichiers journaux pour aider votre client.
Et maintenant, lorsque vous remplacez un peu les noms, vous vous retrouvez avec une implémentation simple d'un localisateur de service , qui est l'un des deux modèles d' inversion de 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 au localisateur de service unique et central.
L'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 service: au lieu que différentes classes demandent au localisateur de service une implémentation pour une interface spécifique, vous - encore une fois - revenez au contrôle sur qui instancie quoi .
Avec l'injection de dépendances, votre Database
classe dispose désormais d'un constructeur qui nécessite un paramètre de type ICanLog
:
public Database(ICanLog logger) { ... }
Maintenant, votre base de données a toujours un enregistreur à utiliser, mais elle ne sait plus d'où vient cet enregistreur.
Et c'est là qu'intervient un framework DI: vous configurez à nouveau vos mappages, puis demandez à votre framework DI d'instancier votre application pour vous. Comme la Application
classe nécessite une ICanPersistData
implémentation, une instance de Database
est injectée - mais pour cela, elle doit d'abord créer une instance du type d'enregistreur configuré pour ICanLog
. Etc ...
Donc, pour faire court: l'injection de dépendances est l'une des deux façons de supprimer les dépendances dans votre code. Il est très utile pour les changements de configuration après la compilation, et c'est une bonne chose pour les tests unitaires (car il est très facile d'injecter des stubs et / ou des mocks).
Dans la pratique, il y a des choses que vous ne pouvez pas faire sans localisateur de service (par exemple, si vous ne savez pas à l'avance combien d'instances vous avez besoin d'une interface spécifique: un framework DI injecte toujours une seule instance par paramètre, mais vous pouvez appeler un localisateur de service à l'intérieur d'une boucle, bien sûr), donc le plus souvent chaque infrastructure DI fournit également un localisateur de service.
Mais en gros, c'est tout.
PS: Ce que j'ai décrit ici est une technique appelée injection de constructeur , il y a aussi l' injection de propriété où pas les paramètres du constructeur, mais les propriétés sont utilisées pour définir et résoudre les dépendances. Considérez l'injection de propriété comme une dépendance facultative et l'injection de constructeur comme des dépendances obligatoires. Mais la discussion à ce sujet dépasse le cadre de cette question.