J'ai récemment travaillé sur un projet Python où nous avons fait beaucoup d'injection de dépendances (parce que nous devons pour que l'application soit testable), mais nous n'avons utilisé aucun framework. Parfois, c'était un peu fastidieux de câbler toutes les dépendances manuellement, mais dans l'ensemble cela a très bien fonctionné.
Lorsqu'un objet devait être créé à plusieurs endroits, nous avions simplement une fonction (parfois une méthode de classe de cet objet) pour créer une instance de production. Cette fonction était appelée chaque fois que nous avions besoin de cet objet.
Dans les tests, nous avons fait la même chose: si plusieurs fois nous devions créer une "version de test" d'un objet (c'est-à-dire une instance réelle soutenue par des objets factices), nous avions une fonction pour créer cette "version de test" d'une classe, à utiliser dans les tests.
Comme je l'ai dit, cela fonctionnait généralement bien.
J'entre maintenant dans un nouveau projet Java, et nous décidons d'utiliser un framework DI ou de faire DI manuellement (comme dans le projet précédent).
Veuillez expliquer en quoi le travail avec un framework DI sera différent, et de quelles manières il est meilleur ou pire que l'injection de dépendance manuelle 'Vanilla'.
Edit: Je voudrais également ajouter à la question: avez-vous été témoin d'un projet de "niveau professionnel" qui fait de l'ID manuellement, sans cadre?
La clé des conteneurs DI est l'abstraction . Les conteneurs DI résument cette préoccupation de conception pour vous. Comme vous pouvez le deviner, cela a un impact notable sur le code, souvent traduit par un nombre moins important de constructeurs, de setters, d'usines et de builders. En conséquence, ils rendent votre code plus faiblement couplé (en raison du sous-jacent IoC ), plus propre et plus simple. Mais pas sans frais.
Il y a des années, une telle abstraction nous obligeait à écrire divers fichiers de configuration ( programmation déclarative ). C'était fantastique de voir le nombre de LOC diminuer considérablement, mais alors que les LOCS diminuaient, le nombre de fichiers de configuration augmentait légèrement. Pour les projets de moyenne ou petite envergure, ce n'était pas du tout un problème, mais pour les grands projets, avoir "l'assemblage du code" dispersé à 50% entre le code et les descripteurs XML est devenu une douleur dans le ... Néanmoins, le principal problème était le le paradigme lui-même. C'était assez rigide car les descripteurs laissaient peu d'espace pour les personnalisations.
Le paradigme s'est progressivement déplacé vers convention sur la configuration , qui échange les fichiers de configuration contre des annotations ou des attributs. Il maintient notre code plus propre et plus simple tout en nous offrant la flexibilité que XML ne pouvait pas.
Il s'agit probablement de la différence la plus importante concernant la façon dont vous travailliez jusqu'à présent. Moins de code et plus de magie.
La convention sur la configuration rend l'abstraction si lourde que vous savez à peine comment elle fonctionne. Cela semble parfois magique et voici encore un inconvénient. Une fois que cela fonctionne, de nombreux développeurs ne se soucient pas de la façon dont cela fonctionne.
C'est un handicap pour ceux qui ont appris l'ID avec des conteneurs DI. Ils ne comprennent pas pleinement la pertinence des modèles de conception, les bonnes pratiques et les principes qui ont été abstraits par le conteneur. J'ai demandé aux développeurs s'ils étaient familiarisés avec DI et ses avantages et ils ont répondu: - Oui, j'ai utilisé Spring IoC -. (Qu'est-ce que ça signifie?!?)
On peut être d'accord ou non avec chacun de ces "inconvénients". À mon humble avis, il est indispensable de savoir ce qui se passe dans votre projet. Sinon, nous ne le comprenons pas complètement. Est aussi de savoir à quoi sert DI et d'avoir une idée de la façon de l'implémenter est un plus à considérer.
Productivité en un mot . Les cadres nous permettent de nous concentrer sur ce qui compte vraiment. Le métier de l'application.
Malgré les lacunes commentées, pour beaucoup d'entre nous (dont le travail est conditionné par les délais et les coûts), ces outils sont une ressource inestimable. À mon avis, cela devrait être notre principal argument en faveur de la mise en œuvre d'un conteneur DI. Productivité .
Que ce soit si nous implémentons des DI purs ou des conteneurs, les tests ne seront pas un problème. Plutôt l'inverse. Les mêmes conteneurs nous fournissent souvent les simulations pour les tests unitaires hors de la boîte. Au fil des ans, j'ai utilisé mes tests comme thermomètres. Ils me disent si je me suis beaucoup appuyé sur les installations de conteneurs. Je dis cela parce que ces conteneurs peuvent initialiser des attributs privés sans setters ni constructeurs. Ils peuvent injecter des composants presque partout!
Cela semble tentant non? Faites attention! Ne tombez pas dans le piège !!
Si vous décidez de mettre en œuvre des conteneurs, je vous suggère fortement de respecter les bonnes pratiques. Continuez à implémenter des constructeurs, des setters et des interfaces. Appliquer le framework/conteneur pour les utiliser . Cela facilitera les migrations possibles vers un autre framework ou supprimera le framework actuel. Cela peut réduire considérablement la dépendance à l'égard du conteneur.
Concernant ces pratiques:
Ma réponse va être biaisée par sa propre expérience qui est principalement en faveur des conteneurs DI (comme je l'ai dit, je suis fortement concentré sur la productivité, donc je n'ai jamais eu besoin d'une DI pure. Bien au contraire).
Je pense que vous trouverez intéressant article de Mark Seeman sur ce sujet, qui répond également à la question concernant comment implémenter la DI pure.
Enfin, si nous parlions de Java, j'envisagerais d'abord de jeter un œil à la JSR330 - Dependency Injection .
Avantages :
Inconvénients :
Différences avec DI pur :
D'après mon expérience, un cadre d'injection de dépendances entraîne un certain nombre de problèmes. Certains problèmes dépendront du cadre et des outils. Il peut y avoir des pratiques qui atténuent les problèmes. Mais ce sont les problèmes que j'ai rencontrés.
Dans certains cas, vous souhaitez injecter un objet global: un objet qui n'aura qu'une seule instance dans votre application en cours d'exécution. Cela peut être un MarkdownToHtmlRendererer
, un DatabaseConnectionPool
, l'adresse de votre serveur api, etc. Pour ces cas, les frameworks d'injection de dépendances fonctionnent très simplement, vous ajoutez simplement la dépendance nécessaire à votre constructeur et vous 'ai compris.
Mais qu'en est-il des objets qui ne sont pas globaux? Et si vous avez besoin de l'utilisateur pour la demande actuelle? Que faire si vous avez besoin de la transaction de base de données actuellement active? Et si vous avez besoin de l'élément HTML correspondant à la div qui contient un formulaire dans lequel se trouve une zone de texte qui vient de déclencher un événement?
La solution que j'ai vue utilisée par les frameworks DI est les injecteurs hiérarchiques. Mais cela rend le modèle d'injection plus compliqué. Il devient plus difficile de comprendre exactement ce qui sera injecté à partir de quelle couche de la hiérarchie. Il devient difficile de dire si un objet donné existera par application, par utilisateur, par demande, etc. Vous ne pouvez plus simplement ajouter vos dépendances et le faire fonctionner.
Souvent, vous voudrez avoir une bibliothèque de code réutilisable. Cela comprendra également les instructions sur la façon de construire des objets à partir de ce code. Si vous utilisez un framework d'injection de dépendances, cela inclura généralement des règles de liaison. Si vous souhaitez utiliser la bibliothèque, vous ajoutez leurs règles de liaison aux vôtres.
Cependant, divers problèmes peuvent en résulter. Vous pouvez vous retrouver avec la même classe liée à différentes implémentations. La bibliothèque peut s'attendre à ce que certaines liaisons existent. La bibliothèque peut remplacer certaines liaisons par défaut, ce qui fait que votre code fait une chose étrange. Votre code peut remplacer une liaison par défaut, ce qui fait que le code de la bibliothèque fait des choses étranges.
Lorsque vous écrivez manuellement des usines, vous avez une idée de la façon dont les dépendances de l'application s'emboîtent. Lorsque vous utilisez un framework d'injection de dépendances, vous ne voyez pas cela. Au lieu de cela, les objets ont tendance à croître de plus en plus de dépendances jusqu'à ce que tout dépend tôt ou tard de tout.
Votre IDE ne comprendra probablement pas le cadre d'injection de dépendances. Avec les usines manuelles, vous pouvez trouver tous les appelants d'un constructeur pour comprendre tous les endroits où l'objet pourrait être construit. Mais il a gagné ' t trouver les appels générés par le framework DI.
Qu'obtenons-nous d'un cadre d'injection de dépendances? Nous arrivons à éviter un passe-partout simple nécessaire pour instancier des objets. Je suis pour l'élimination du passe-partout simple. Cependant, le maintien d'un passe-partout simple d'esprit est facile. Si nous voulons remplacer ce passe-partout, nous devons insister pour le remplacer par quelque chose de plus facile. En raison des diverses questions dont j'ai discuté ci-dessus, je ne pense pas que les cadres (du moins en l'état) correspondent au projet de loi.
Non.
La construction d'objets se passe dans un langage qui n'est pas Java.
Si les méthodes d'usine sont assez bonnes pour vos besoins, vous pouvez faire DI dans Java complètement sans framework en Java pojo 100% authentique. Si vos besoins vont au-delà, vous avez d'autres options.
Les modèles de conception de Gang of Four accomplissent beaucoup de grandes choses, mais ont toujours eu des faiblesses en ce qui concerne les modèles de création. Java lui-même a quelques faiblesses ici. Les frameworks DI aident vraiment à cette faiblesse créative plus qu'ils ne le font avec les injections réelles. Vous savez déjà comment faire cela sans eux. Combien de bien ils vous feront dépend de l'aide dont vous avez besoin pour la construction des objets.
Il existe de nombreux modèles de création qui sont apparus après le Gang of Four. Le générateur Josh Bloch est un merveilleux hack autour du manque de paramètres nommés de Java. Au-delà de cela, les constructeurs iDSL offrent beaucoup de flexibilité. Mais chacun d'entre eux représente un travail et un passe-partout importants.
Les cadres peuvent vous sauver d'une partie de cet ennui. Ils ne sont pas sans inconvénients. Si vous ne faites pas attention, vous pouvez vous retrouver dépendant du framework. Essayez de changer de framework après en avoir utilisé un et voyez par vous-même. Même si vous conservez en quelque sorte le xml ou les annotations génériques, vous pouvez finir par prendre en charge ce qui sont essentiellement deux langues que vous devez mentionner côte à côte lorsque vous publiez des travaux de programmation.
Avant de devenir trop amoureux de la puissance des frameworks DI, prenez un peu de temps et étudiez d'autres frameworks qui vous offrent des capacités que vous pourriez autrement penser avoir besoin d'un framework DI. Beaucoup se sont diversifiés au-delà de DI simple . Considérez: Lombok , AspectJ , et slf4J . Chacun chevauche certains cadres DI, mais chacun fonctionne très bien seul.
Si le test est vraiment la force motrice derrière cela, veuillez regarder: jUnit(toute xUnit vraiment) et Mockito(toute maquette vraiment ) avant de commencer à penser qu'un cadre DI devrait prendre le contrôle de votre vie.
Vous pouvez faire ce que vous voulez, mais pour un travail sérieux, je préfère une boîte à outils bien remplie à un couteau suisse. J'aimerais qu'il soit plus facile de dessiner ces lignes, mais certains ont travaillé dur pour les brouiller. Rien de mal à cela, mais cela peut rendre ces choix difficiles.
La création de classes et leur affectation de dépendances fait partie du processus de modélisation, les parties amusantes. C'est la raison pour laquelle la plupart des programmeurs aiment la programmation.
Décider quelle implémentation sera utilisée et comment les classes sont construites est une chose qui devra être implémentée d'une manière ou d'une autre et fera partie de la configuration de l'application.
L'écriture manuelle des usines est un travail fastidieux. Les infrastructures DI facilitent la tâche en résolvant automatiquement les dépendances qui peuvent être résolues et en exigeant une configuration pour celles qui ne le sont pas.
L'utilisation d'IDE modernes vous permet de parcourir les méthodes et leurs utilisations. Avoir des usines écrites manuellement est assez bon, car vous pouvez simplement mettre en évidence le constructeur d'une classe, montrer ses usages, et cela vous montrera tous les endroits où et comment la classe spécifique est en cours de construction.
Mais même avec le framework DI, vous pouvez avoir une place centrale, comme la configuration de construction de graphe d'objet (OGCC à partir de maintenant), et si une classe dépend de dépendances ambiguës, vous pouvez simplement regarder OGCC et voir comment une classe spécifique est en cours de construction juste là.
Vous pourriez objecter, que vous pensez personnellement pouvoir voir les usages d'un constructeur spécifique est un grand plus. Je dirais que ce qui ressort mieux pour vous n'est pas seulement subjectif, mais vient surtout de l'expérience personnelle.
Si vous aviez toujours travaillé sur des projets qui s'appuyaient sur des frameworks DI, vous ne le sauriez vraiment que lorsque vous vouliez voir une configuration d'une classe, vous regarderiez toujours OGCC et n'essaieriez même pas de voir comment les constructeurs sont utilisés , car vous savez que les dépendances sont toutes câblées automatiquement de toute façon.
Naturellement, les frameworks DI ne peuvent pas être utilisés pour tout et de temps en temps, vous devrez écrire une ou deux méthodes d'usine. Un cas très courant est la construction d'une dépendance pendant l'itération sur une collection, où chaque itération fournit un indicateur différent qui devrait produire une implémentation différente d'une interface par ailleurs commune.
En fin de compte, tout dépendra très probablement de vos préférences et de ce que vous êtes prêt à sacrifier (soit en écrivant du temps dans les usines, soit en transparence).
Parlant en tant que développeur PHP, bien que j'aime l'idée derrière les frameworks DI, je ne les ai utilisés qu'une seule fois. C'est à ce moment-là qu'une équipe avec laquelle je travaillais était censée déployer une application très rapidement et nous ne pouvions pas perdre de temps à écrire des usines. Nous avons donc simplement choisi un framework DI, l'avons configuré et cela a fonctionné, en quelque sorte.
Pour la plupart des projets, j'aime que les usines soient écrites manuellement parce que je n'aime pas la magie noire qui se passe dans les frameworks DI. J'étais le responsable de la modélisation d'une classe et de ses dépendances. C'est moi qui décidais pourquoi le design ressemblait à ça. Je serai donc celui qui construit correctement la classe (même si la classe prend des dépendances dures, telles que des classes concrètes au lieu d'interfaces).
Personnellement, je n'utilise pas de conteneurs DI et je ne les aime pas. J'ai deux autres raisons à cela.
Il s'agit généralement d'une dépendance statique. C'est donc juste un localisateur de services avec tous ses inconvénients. Ensuite, en raison de la nature des dépendances statiques, elles sont masquées. Vous n'avez pas besoin de vous en occuper, ils sont déjà en quelque sorte là, et c'est dangereux, car vous cessez de les contrôler.
Il casse OOP principes , comme la cohésion et la métaphore de l'objet Lego-brick de David West ).
Donc, construire tout l'objet aussi près que possible de le point d'entrée du programme est la voie à suivre.
Comme vous l'avez déjà remarqué: en fonction de la langue que vous utilisez, il existe différents points de vue, différentes manières de gérer des problèmes similaires.
En ce qui concerne l'injection de dépendances, cela se résume à ceci: l'injection de dépendances n'est, comme le dit le terme, rien de plus que mettre les dépendances dont un objet a besoin into cet objet - contrastant dans l'autre sens: laisser l'objet lui-même instancier des instances d'objets dont il dépend. Vous dissociez la création d'objets et la consommation d'objets pour gagner en flexibilité.
Si votre conception est bien structurée, vous avez généralement peu de classes avec de nombreuses dépendances, donc le câblage des dépendances - à la main ou par un framework - devrait être relativement facile et la quantité de passe-partout pour écrire les tests devrait être facile à gérer.
Cela dit, il n'y a pas besoin d'un cadre DI.
Mais pourquoi voulez-vous quand même utiliser un framework?
I) Évitez le code passe-partout
Si votre projet prend de la taille, où vous ressentez la douleur d'écrire des usines (et des instatiations) encore et encore, vous devez utiliser un cadre DI
II) Approche déclinative de la programmation
Quand Java était autrefois une programmation avec XML c'est maintenant: saupoudrant de fairydust avec les annotations et la magie se produisent .
Mais sérieusement: je vois clairement à l'envers à cela. Vous communiquez clairement votre intention en disant ce qui est nécessaire plutôt que où le trouver et comment le construire.
tl; dr
Les frameworks DI n'améliorent pas votre base de code comme par magie, mais cela aide à rendre le code beau vraiment net . Utilisez-le lorsque vous en ressentez le besoin. À un certain moment de votre projet, cela vous fera gagner du temps et évitera les nausées.
Oui. Vous devriez probablement lire le livre de Mark Seemann intitulé "Dependency Injection". Il décrit quelques bonnes pratiques. D'après certains de vos propos, vous pourriez en bénéficier. Vous devez viser à avoir une racine de composition ou une racine de composition par unité de travail (par exemple, une demande Web). J'aime sa façon de penser que tout objet que vous créez est considéré comme une dépendance (tout comme n'importe quel paramètre d'une méthode/constructeur), vous devez donc faire très attention où et comment vous créez des objets. Un cadre vous aidera à garder ces concepts clairs. Si vous lisez le livre de Mark, vous trouverez plus que la réponse à votre question.
Oui, lorsque je travaille en Java, je recommanderais un framework DI. Il y a plusieurs raisons à cela:
Java a plusieurs très bons packages DI qui offrent une injection de dépendances automatisée et sont également généralement fournis avec des fonctionnalités supplémentaires qui sont plus difficiles à écrire manuellement que les DI simples (par exemple, les dépendances étendues qui permettent une connexion automatique entre les étendues en générant du code pour rechercher dans quelle étendue devrait être utilisez un d pour trouver la version correcte de la dépendance pour cette étendue - ainsi, par exemple, les objets de portée d'application peuvent se référer à ceux de portée de demande).
Les annotations Java sont extrêmement utiles et fournissent un excellent moyen de configurer les dépendances
La construction d'objets Java est trop verbeuse par rapport à ce à quoi vous êtes habitué en python; l'utilisation d'un cadre ici permet d'économiser plus de travail que dans un langage dynamique.
Les frameworks Java DI peuvent faire des choses utiles comme générer automatiquement des proxys et des décorateurs qui fonctionnent davantage en Java qu'un langage dynamique.
Si votre projet est assez petit et que vous n'avez pas beaucoup d'expérience avec le DI-Framework, je recommanderais de ne pas utiliser le framework et de le faire manuellement.
Raison: J'ai déjà travaillé dans un projet qui utilisait deux bibliothèques qui avaient des dépendances directes avec différents cadres di. ils avaient tous deux un code spécifique au framework comme di-give-me-an-instance-of-XYZ. Autant que je sache, vous ne pouvez avoir qu'une seule implémentation active de di-framework à la fois.
avec un code comme celui-ci, vous pouvez faire plus de mal que d'avantages.
Si vous avez plus d'expérience, vous supprimerez probablement le di-framwork par une interface (usine ou servicelocator) de sorte que ce problème n'existera plus et que le cadre di profitera davantage à ce mal.