Je lis sur l' injection de dépendance (DI). Pour moi, c'est une chose très compliquée à faire, car je lisais que c'était aussi une référence à l' inversion de contrôle (IoC) et je me suis dit que j'allais partir en voyage.
C’est ce que je comprends: au lieu de créer un modèle dans la classe qui le consomme également, vous passez (injectez) le modèle (déjà rempli de propriétés intéressantes) à l’endroit où il est nécessaire (à une nouvelle classe qui pourrait le prendre comme paramètre dans le constructeur).
Pour moi, cela ne fait que passer un argument. Je dois avoir mal compris le point? Peut-être que cela devient plus évident avec de plus gros projets?
Ma compréhension est non-DI (en utilisant un pseudo-code):
public void Start()
{
MyClass class = new MyClass();
}
...
public MyClass()
{
this.MyInterface = new MyInterface();
}
Et DI serait
public void Start()
{
MyInterface myInterface = new MyInterface();
MyClass class = new MyClass(myInterface);
}
...
public MyClass(MyInterface myInterface)
{
this.MyInterface = myInterface;
}
Quelqu'un pourrait-il faire la lumière, car je suis sûr que je suis embrouillé ici.
la source
Réponses:
Eh bien oui, vous injectez vos dépendances, soit par un constructeur, soit par des propriétés.
Une des raisons en est de ne pas encombrer MyClass avec les détails de la manière dont une instance de MyInterface doit être construite. MyInterface pourrait être quelque chose qui contient toute une liste de dépendances et le code de MyClass deviendrait très rapidement moche si vous instanciez toutes les dépendances de MyInterface dans MyClass.
Une autre raison est pour les tests.
Si vous avez une dépendance sur une interface de lecteur de fichiers et que vous l'injectez via un constructeur dans, par exemple, ConsumerClass, cela signifie que lors des tests, vous pouvez passer une implémentation en mémoire du lecteur de fichiers à la ConsumerClass, évitant ainsi la nécessité de: faire I / O pendant les tests.
la source
La mise en œuvre de DI dépend beaucoup du langage utilisé.
Voici un exemple simple non-DI:
Cela est nul, par exemple, pour un test, je veux utiliser un objet fictif
bar
. Nous pouvons donc rendre cela plus flexible et permettre aux instances d'être transmises via le constructeur:C'est déjà la forme la plus simple d'injection de dépendance. Mais cela reste quand même gênant parce que tout doit être fait manuellement (également, parce que l'appelant peut détenir une référence à nos objets internes et invalider ainsi notre état).
Nous pouvons introduire plus d'abstraction en utilisant des usines:
Une option consiste
Foo
à générer une usine abstraite.Une autre option consiste à passer une fabrique au constructeur:
Les problèmes restants avec cela sont que nous devons écrire une nouvelle
DependencyManager
sous-classe pour chaque configuration et que le nombre de dépendances pouvant être gérées est assez limité (chaque nouvelle dépendance nécessite une nouvelle méthode dans l'interface).Avec des fonctionnalités telles que la réflexion et le chargement de classe dynamique, nous pouvons éviter ce problème. Mais cela dépend beaucoup de la langue utilisée. En Perl, les classes peuvent être référencées par leur nom, et je pourrais simplement faire
Dans des langages tels que Java, le gestionnaire de dépendances pourrait se comporter comme un
Map<Class, Object>
:La classe à laquelle le problème
Bar.class
est résolu peut être configurée au moment de l'exécution, par exemple en maintenant uneMap<Class, Class>
interface qui mappe les interfaces aux implémentations.Il y a toujours un élément manuel impliqué dans l'écriture du constructeur. Mais nous pouvons rendre le constructeur complètement inutile, par exemple en pilotant la DI via des annotations:
Voici un exemple d'implémentation d'un framework DI minuscule (et très restreint): http://ideone.com/b2ubuF , bien que cette implémentation soit totalement inutilisable pour les objets immuables (cette implémentation naïve ne peut prendre aucun paramètre pour le constructeur).
la source
Nos amis de Stack Overflow ont une bonne réponse à cela . Mon préféré est la deuxième réponse, y compris la citation:
du blog de James Shore . Bien sûr, il existe des versions / modèles plus complexes qui se superposent, mais cela suffit pour comprendre fondamentalement ce qui se passe. Au lieu qu'un objet crée ses propres variables d'instance, elles sont transmises de l'extérieur.
la source
Supposons que vous faites la décoration intérieure de votre salon: vous installez un lustre élégant au plafond et branchez un lampadaire élégant et assorti. Un an plus tard, votre charmante épouse décide qu'elle aimerait que la pièce soit un peu moins formelle. Quel luminaire va-t-il être plus facile à changer?
L’idée de DI est d’utiliser l’approche du «plug-in» partout. Cela peut ne pas sembler un gros problème si vous vivez dans une simple cabane sans cloison sèche et tous les fils électriques exposés. (Vous êtes un homme à tout faire - vous pouvez changer n'importe quoi!) Et de même, dans une application petite et simple, DI peut ajouter plus de complications que cela ne vaut.
Mais pour une application large et complexe, DI est indispensable pour une maintenance à long terme. Il vous permet de "débrancher" des modules entiers de votre code (le backend de la base de données, par exemple) et de les échanger avec des modules différents qui accomplissent la même chose, mais de manière totalement différente (un système de stockage en nuage, par exemple).
BTW, une discussion de DI serait incomplète sans une recommandation de Dependency Injection in .NET , par Mark Seeman. Si vous connaissez bien .NET et avez déjà l’intention de vous impliquer dans le développement de logiciels à grande échelle, c’est une lecture essentielle. Il explique le concept bien mieux que moi.
Laissez-moi vous laisser avec une dernière caractéristique essentielle de DI que votre exemple de code néglige. DI vous permet d’exploiter le vaste potentiel de flexibilité contenu dans l’adage " Programme aux interfaces ". Pour modifier légèrement votre exemple:
Mais MAINTENANT, car il
MyRoom
est conçu pour l’ interface ILightFixture , je peux facilement passer l’année prochaine et modifier une ligne de la fonction Main ("injecter une" dépendance "différente), et obtenir instantanément de nouvelles fonctionnalités dans MyRoom (sans avoir à reconstruisez ou redéployez MyRoom, s'il se trouve dans une bibliothèque séparée (cela suppose, bien entendu, que toutes vos implémentations ILightFixture soient conçues avec Liskov Substitution à l'esprit). Tous, avec juste un simple changement:De plus, je peux maintenant effectuer des tests unitaires
MyRoom
(vous êtes des tests unitaires, non?) Et utiliser un dispositif d'éclairage "simulé", ce qui me permet d'effectuer des testsMyRoom
entièrement indépendants deClassyChandelier.
Bien sûr, il y a beaucoup plus d'avantages, mais ce sont ceux qui m'ont vendu l'idée.
la source
L'injection de dépendances consiste simplement à transmettre un paramètre, mais cela pose néanmoins quelques problèmes et le nom a pour but d'expliquer un peu pourquoi la différence entre créer un objet et en transmettre un est importante.
C'est important parce que (de toute évidence) n'importe quel objet temporel A appelle le type B, A dépend du fonctionnement de B. Ce qui signifie que si A décide quel type concret de B il utilisera, beaucoup de flexibilité dans la manière dont A peut être utilisé par des classes extérieures, sans modifier A, a été perdue.
Je mentionne cela parce que vous parlez de "rater le but" de l'injection de dépendance, et cela explique en grande partie pourquoi les gens aiment le faire. Cela peut signifier simplement passer un paramètre, mais cela peut être important.
En outre, certaines des difficultés rencontrées lors de la mise en œuvre de la DI se révèlent mieux dans les grands projets. En règle générale, vous déciderez des classes concrètes à utiliser jusqu'au sommet. Votre méthode de démarrage peut donc ressembler à:
mais avec 400 autres lignes de ce genre de code fascinant. Si vous imaginez que cela continue avec le temps, l'attrait des conteneurs DI devient plus évident.
En outre, imaginez une classe qui, par exemple, implémente IDisposable. Si les classes qui l'utilisent le reçoivent par injection plutôt que de le créer elles-mêmes, comment savent-elles quand appeler Dispose ()? (Ce qui est un autre problème qu'un conteneur DI traite.)
Donc, bien sûr, l'injection de dépendance ne fait que transmettre un paramètre, mais lorsque l'on parle d'injection de dépendance, on entend réellement "transmettre un paramètre à votre objet, plutôt que de faire en sorte que votre objet instancie quelque chose lui-même", et traite de tous les avantages des inconvénients de une telle approche, éventuellement à l’aide d’un conteneur DI.
la source
Au niveau de la classe, c'est facile.
"Dependency Injection" répond simplement à la question "comment vais-je trouver mes collaborateurs" avec "ils sont poussés à vous - vous n'avez pas à aller les chercher vous-même". (Cela ressemble à - mais pas la même chose que - 'Inversion of Control' où la question "comment vais-je ordonner mes opérations sur mes entrées?" A une réponse similaire).
Le seul avantage de vos collaborateurs est que le code client peut utiliser votre classe pour composer un graphe d'objet adapté à ses besoins actuels ... vous n'avez pas prédéterminé de manière arbitraire la forme et la mutabilité du graphe en décidant en privé les types concrets et le cycle de vie de vos collaborateurs.
(Tous ces autres avantages, de testabilité, de couplage lâche, etc., découlent en grande partie de l'utilisation d'interfaces et pas tellement de la dépendance par injection, bien que la DI favorise naturellement l'utilisation d'interfaces).
Il est à noter que, si vous évitez d'instancier vos propres collaborateurs, votre classe doit donc obtenir ses collaborateurs d'un constructeur, d'une propriété ou d'un argument de méthode (cette dernière option est souvent négligée, d'ailleurs ... Il n’est pas toujours logique que les collaborateurs d’une classe fassent partie de son «état»).
Et c'est une bonne chose.
Au niveau de l'application ...
Voilà pour la vue par classe des choses. Supposons que plusieurs classes suivent la règle "ne pas instancier vos propres collaborateurs" et que vous souhaitiez en faire une application. La solution la plus simple consiste à utiliser le bon vieux code (un outil très utile pour appeler des constructeurs, des propriétés et des méthodes!) Afin de composer le graphe d'objet souhaité et de lancer des entrées. (Oui, certains objets de votre graphe seront eux-mêmes des fabriques d’objets, qui ont été transmises en tant que collaborateurs à d’autres objets de longue durée dans le graphe, prêts au service. Vous ne pouvez pas pré-construire tous les objets! ).
... votre besoin de configurer de manière "flexible" le graphe d'objet de votre application ...
En fonction de vos autres objectifs (non liés au code), vous pouvez donner à l'utilisateur final un contrôle sur le graphe d'objets ainsi déployé. Cela vous conduit vers un schéma de configuration, d’une sorte ou d’une autre, qu’il s’agisse d’un fichier texte de votre propre conception avec des paires nom / valeur, un fichier XML, un DSL personnalisé, un langage de description de graphe déclaratif tel que YAML, un langage de script impératif tel que JavaScript ou autre chose appropriée à la tâche à accomplir. Peu importe ce qu'il faut pour composer un graphe d'objet valide, de manière à répondre aux besoins de vos utilisateurs.
... peut être une force de conception significative.
Dans les circonstances les plus extrêmes de ce type, vous pouvez choisir une approche très générale et donner aux utilisateurs finaux un mécanisme général permettant de «câbler» le graphe d'objet de leur choix et même de leur permettre de fournir des réalisations concrètes d'interfaces au runtime! (Votre documentation est un joyau étincelant, vos utilisateurs sont très intelligents, ils connaissent au moins les grandes lignes du graphe d'objets de votre application, mais ils ne disposent pas d'un compilateur à portée de main). Ce scénario peut théoriquement se produire dans certaines situations «d'entreprise».
Dans ce cas, vous disposez probablement d'un langage déclaratif qui permet à vos utilisateurs d'exprimer n'importe quel type, de la composition d'un graphe d'objets de ce type et d'une palette d'interfaces que l'utilisateur final mythique peut mélanger et assortir. Pour réduire la charge cognitive de vos utilisateurs, vous préférez une approche de «configuration par convention», de sorte qu'ils n'aient plus qu'à intervenir et à passer outre le fragment objet-graphe d'intérêt, au lieu de se débattre dans son ensemble.
Vous pauvre pauvre merde!
Parce que vous n'avez pas envie d'écrire tout cela vous-même (mais sérieusement, vérifiez une liaison YAML pour votre langue), vous utilisez un cadre DI quelconque.
En fonction de la maturité de ce cadre, vous n’avez peut-être pas la possibilité d’utiliser une injection par constructeur, même lorsque cela a du sens (les collaborateurs ne changent pas pendant la durée de vie d’un objet), vous obligeant ainsi à utiliser Setter Injection (même lorsque les collaborateurs ne changent pas au cours de la vie d'un objet, et même lorsqu'il n'y a pas vraiment de raison logique pour laquelle toute implémentation concrète d'une interface doit avoir des collaborateurs d'un type spécifique). Si c'est le cas, vous êtes actuellement dans l'enfer à fort couplage, malgré les "interfaces utilisées" avec diligence dans votre code-base - horreur!
Espérons cependant que vous avez utilisé un framework DI qui vous offre une option d’injection de constructeur, et que vos utilisateurs ne sont que grincheux de ne pas perdre plus de temps à réfléchir aux choses spécifiques qu’ils ont besoin de configurer et à leur donner une interface utilisateur plus adaptée à la tâche. à portée de main. (Bien que pour être juste, vous avez probablement essayé de penser à un moyen, mais JavaEE vous a laissé tomber et vous avez dû recourir à ce hack horrible).
Note de démarrage
Vous n’utilisez jamais Google Guice, ce qui permet au codeur de se passer de la tâche de composer un graphe-objet avec du code ... en écrivant du code. Argh!
la source
Beaucoup de gens ont dit ici que DI est un mécanisme pour externaliser l’initialisation d’une variable d’instance.
Pour des exemples évidents et simples, vous n'avez pas vraiment besoin d'une "injection de dépendance". Vous pouvez le faire en transmettant un simple paramètre au constructeur, comme vous l'avez indiqué plus haut.
Cependant, je pense qu'un mécanisme "d'injection de dépendance" dédié est utile lorsque vous parlez de ressources au niveau de l'application, lorsque l'appelant et l'appelé ne savent rien de ces ressources (et ne veulent pas savoir!).
Par exemple: connexion à la base de données, contexte de sécurité, fonction de journalisation, fichier de configuration, etc.
De plus, chaque singleton est en général un bon candidat pour DI. Plus vous en avez et en utilisez, plus vous aurez besoin de DI.
Pour ce type de ressources, il est clair qu’il n’est pas logique de les déplacer dans des listes de paramètres, c’est pourquoi la DI a été inventée.
Dernière remarque, vous avez également besoin d'un conteneur DI, qui effectuera l'injection proprement dite ... (encore une fois, pour libérer l'appelant et l'appelé des détails de la mise en œuvre).
la source
Si vous transmettez un type de référence abstrait, le code appelant peut transmettre tout objet qui étend / implémente le type abstrait. Ainsi, à l'avenir, la classe qui utilise l'objet pourrait utiliser un nouvel objet créé sans jamais modifier son code.
la source
C'est une autre option en plus de la réponse d'Amon
Utiliser les constructeurs:
C'est ce que j'utilise pour les dépendances. Je n'utilise aucun cadre DI. Ils se mettent en travers du chemin.
la source