J'essaie de comprendre les injections de dépendance (DI), et encore une fois j'ai échoué. Cela semble juste idiot. Mon code n'est jamais un gâchis; J'écris à peine des fonctions et des interfaces virtuelles (bien que je le fasse une fois dans une lune bleue) et toute ma configuration est magiquement sérialisée en classe en utilisant json.net (parfois en utilisant un sérialiseur XML).
Je ne comprends pas très bien quel problème cela résout. Cela ressemble à une façon de dire: "salut. Lorsque vous exécutez cette fonction, renvoyez un objet de ce type et utilise ces paramètres / données."
Mais ... pourquoi devrais-je jamais l'utiliser? Remarque Je n'ai jamais eu besoin de l'utiliser object
aussi, mais je comprends à quoi ça sert.
Quelles sont les situations réelles dans la construction d'un site Web ou d'une application de bureau où l'on utiliserait DI? Je peux facilement trouver des cas pour lesquels quelqu'un peut vouloir utiliser des interfaces / fonctions virtuelles dans un jeu, mais il est extrêmement rare (assez rare que je ne me souvienne pas d'une seule instance) d'utiliser cela dans du code non-jeu.
la source
Réponses:
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:
(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
ouIPrintable
. Une classe est une implémentation réelle d'une ou plusieurs de ces interfaces:List
ouMap
peut toutes deux être des implémentations deIEnumerable
.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 classeLogger
, puisDatabase
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: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
par:
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 à: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: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:
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 typeICanLog
: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 uneICanPersistData
implémentation, une instance deDatabase
est injectée - mais pour cela, elle doit d'abord créer une instance du type d'enregistreur configuré pourICanLog
. 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.
la source
Je pense que la plupart du temps, les gens se trompent sur la différence entre l' injection de dépendance et un cadre d' injection de dépendance (ou un conteneur comme on l'appelle souvent).
L'injection de dépendance est un concept très simple. Au lieu de ce code:
vous écrivez du code comme ceci:
Et c'est tout. Sérieusement. Cela vous donne une tonne d'avantages. Deux importantes sont la possibilité de contrôler les fonctionnalités à partir d'un emplacement central (la
Main()
fonction) au lieu de les diffuser dans tout votre programme, et la possibilité de tester plus facilement chaque classe de manière isolée (car vous pouvez plutôt passer des simulations ou d'autres objets truqués à son constructeur d'une valeur réelle).L'inconvénient, bien sûr, est que vous avez maintenant une méga-fonction qui connaît toutes les classes utilisées par votre programme. C'est ce avec quoi les frameworks DI peuvent aider. Mais si vous ne parvenez pas à comprendre pourquoi cette approche est utile, je vous recommande de commencer par l'injection manuelle de dépendances, afin que vous puissiez mieux comprendre ce que les différents cadres peuvent faire pour vous.
la source
Comme les autres réponses l'ont indiqué, l'injection de dépendances est un moyen de créer vos dépendances en dehors de la classe qui l'utilise. Vous les injectez de l'extérieur et prenez le contrôle de leur création loin de l'intérieur de votre classe. C'est aussi pourquoi l'injection de dépendances est une réalisation du principe d'inversion de contrôle (IoC).
IoC est le principe, où DI est le modèle. La raison pour laquelle vous pourriez "avoir besoin de plus d'un enregistreur" n'est jamais réellement rencontrée, d'après mon expérience, mais la vraie raison est que vous en avez vraiment besoin, chaque fois que vous testez quelque chose. Un exemple:
Ma fonction:
Vous pouvez tester ceci comme ceci:
Donc quelque part dans le
OfferWeasel
, il vous construit un objet d'offre comme celui-ci:Le problème ici est que ce test échouera très probablement toujours, car la date qui est définie sera différente de la date affirmée, même si vous venez de mettre
DateTime.Now
le code de test, il pourrait être désactivé par quelques millisecondes et sera donc échouent toujours. Une meilleure solution serait maintenant de créer une interface pour cela, qui vous permet de contrôler l'heure qui sera définie:L'interface est l'abstraction. L'un est la vraie chose, et l'autre vous permet de simuler un certain temps là où cela est nécessaire. Le test peut ensuite être modifié comme suit:
Comme ça, vous avez appliqué le principe de "l'inversion de contrôle", en injectant une dépendance (obtention de l'heure courante). La principale raison pour cela est de faciliter les tests unitaires isolés, il existe d'autres façons de le faire. Par exemple, une interface et une classe ici ne sont pas nécessaires car en C #, les fonctions peuvent être transmises en tant que variables, donc au lieu d'une interface, vous pouvez utiliser a
Func<DateTime>
pour obtenir la même chose. Ou, si vous adoptez une approche dynamique, vous passez juste n'importe quel objet qui a la méthode équivalente ( typage du canard ), et vous n'avez pas du tout besoin d'une interface.Vous n'aurez presque jamais besoin de plus d'un enregistreur. Néanmoins, l'injection de dépendances est essentielle pour le code typé statiquement tel que Java ou C #.
Et ... Il convient également de noter qu'un objet ne peut remplir correctement son objectif au moment de l'exécution, si toutes ses dépendances sont disponibles, donc il n'y a pas beaucoup d'utilité dans la configuration de l'injection de propriété. À mon avis, toutes les dépendances doivent être satisfaites lorsque le constructeur est appelé, donc l'injection de constructeur est la chose à faire.
J'espère que cela a aidé.
la source
Je pense que la réponse classique est de créer une application plus découplée, qui ne sait pas quelle implémentation sera utilisée pendant l'exécution.
Par exemple, nous sommes un fournisseur de paiement central, travaillant avec de nombreux fournisseurs de paiement dans le monde. Cependant, lorsqu'une demande est faite, je n'ai aucune idée du processeur de paiement que je vais appeler. Je pourrais programmer une classe avec une tonne de boîtiers de commutation, tels que:
Imaginez maintenant que vous devrez maintenant conserver tout ce code dans une seule classe car il n'est pas découplé correctement, vous pouvez imaginer que pour chaque nouveau processeur que vous prendrez en charge, vous devrez créer un nouveau cas if // switch pour chaque méthode, cela ne fait que se compliquer, cependant, en utilisant l'injection de dépendance (ou l'inversion de contrôle - comme on l'appelle parfois, ce qui signifie que celui qui contrôle l'exécution du programme n'est connu qu'au moment de l'exécution, et non pas de complication), vous pourriez réaliser quelque chose très soigné et maintenable.
** Le code ne se compile pas, je sais :)
la source
new ThatProcessor()
plutôt que d'utiliser un cadreLa principale raison d'utiliser DI est que vous voulez mettre la responsabilité de la connaissance de l'implémentation là où la connaissance est là. L'idée de DI est très en ligne avec l'encapsulation et la conception par interface. Si le serveur frontal demande au serveur principal certaines données, il est sans importance pour le serveur principal comment le serveur principal résout cette question. Cela dépend du gestionnaire de demandes.
Cela est déjà courant dans la POO depuis longtemps. Plusieurs fois, la création de morceaux de code comme:
L'inconvénient est que la classe d'implémentation est toujours codée en dur, d'où la connaissance initiale de l'implémentation utilisée. DI pousse la conception par interface un peu plus loin, que la seule chose que le frontal doit savoir est la connaissance de l'interface. Entre le DYI et le DI se trouve le modèle d'un localisateur de service, car le frontal doit fournir une clé (présente dans le registre du localisateur de service) pour permettre à sa demande d'être résolue. Exemple de localisateur de service:
Exemple DI:
L'une des exigences de DI est que le conteneur doit pouvoir savoir quelle classe est l'implémentation de quelle interface. Par conséquent, un conteneur DI nécessite une conception fortement typée et une seule implémentation pour chaque interface à la fois. Si vous avez besoin de plus d'implémentations d'une interface en même temps (comme une calculatrice), vous avez besoin du localisateur de service ou du modèle de conception d'usine.
D (b) I: Injection de dépendance et conception par interface. Cette restriction n'est cependant pas un très gros problème pratique. L'avantage d'utiliser D (b) I est qu'il sert la communication entre le client et le fournisseur. Une interface est une perspective sur un objet ou un ensemble de comportements. Cette dernière est cruciale ici.
Je préfère l'administration des contrats de service avec D (b) I dans le codage. Ils devraient aller ensemble. L'utilisation de D (b) I comme solution technique sans gestion organisationnelle des contrats de service n'est pas très bénéfique à mon point de vue, car DI n'est alors qu'une couche supplémentaire d'encapsulation. Mais lorsque vous pouvez l'utiliser avec l'administration organisationnelle, vous pouvez vraiment utiliser le principe d'organisation D (b) I proposé. Il peut vous aider à long terme à structurer la communication avec le client et d'autres services techniques sur des sujets tels que les tests, le versioning et le développement d'alternatives. Lorsque vous avez une interface implicite comme dans une classe codée en dur, elle est beaucoup moins communicable dans le temps que lorsque vous la rendez explicite à l'aide de D (b) I. Tout se résume à la maintenance, qui se fait au fil du temps et non à la fois. :-)
la source