J'essaie d'introduire DI en tant que modèle ici au travail et l'un de nos principaux développeurs aimerait savoir: quels sont, le cas échéant, les inconvénients de l'utilisation du modèle d'injection de dépendance?
Notez que je cherche ici une liste - si possible - exhaustive, pas une discussion subjective sur le sujet.
Clarification : je parle du modèle d' injection de dépendances (voir cet article de Martin Fowler), pas d' un cadre spécifique, qu'il soit basé sur XML (comme Spring) ou basé sur du code (tel que Guice), ou "auto-rolled" .
Edit : Quelques grandes discussions / diatribes / débats en cours / r / programmation ici.
Réponses:
Quelques points:
Généralement, l'avantage du découplage rend chaque tâche plus simple à lire et à comprendre, mais augmente la complexité de l'orchestration des tâches les plus complexes.
la source
Le même problème de base que vous rencontrez souvent avec la programmation orientée objet, les règles de style et à peu près tout le reste. Il est possible - très courant, en fait - de faire trop d'abstraction, d'ajouter trop d'indirection et d'appliquer généralement de bonnes techniques de manière excessive et aux mauvais endroits.
Chaque modèle ou autre construction que vous appliquez apporte de la complexité. L'abstraction et l'indirection dispersent les informations, déplaçant parfois les détails non pertinents hors du chemin, mais rendant également parfois plus difficile de comprendre exactement ce qui se passe. Chaque règle que vous appliquez apporte de la rigidité, excluant les options qui pourraient être la meilleure approche.
Le but est d'écrire du code qui fait le travail et qui est robuste, lisible et maintenable. Vous êtes un développeur de logiciels - pas un constructeur de tours d'ivoire.
Liens pertinents
http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx
http://www.joelonsoftware.com/articles/fog0000000018.html
La forme d'injection de dépendance la plus simple (ne riez pas) est probablement un paramètre. Le code dépendant dépend des données, et ces données sont injectées par le biais du passage du paramètre.
Oui, c'est idiot et cela ne traite pas du point d'injection de dépendance orienté objet, mais un programmeur fonctionnel vous dira que (si vous avez des fonctions de première classe), c'est le seul type d'injection de dépendance dont vous avez besoin. Le point ici est de prendre un exemple trivial et de montrer les problèmes potentiels.
Prenons cette simple fonction traditionnelle - la syntaxe C ++ n'est pas significative ici, mais je dois l'épeler en quelque sorte ...
J'ai une dépendance que je veux extraire et injecter - le texte "Hello World". Assez facile...
Comment est-ce plus rigide que l'original? Et si je décide que la sortie doit être unicode. Je veux probablement passer de std :: cout à std :: wcout. Mais cela signifie que mes cordes doivent alors être de wchar_t, pas de char. Soit chaque appelant doit être modifié, soit (plus raisonnablement), l'ancienne implémentation est remplacée par un adaptateur qui traduit la chaîne et appelle la nouvelle implémentation.
Il s'agit là de travaux de maintenance qui ne seraient pas nécessaires si nous conservions l'original.
Et si cela semble trivial, jetez un œil à cette fonction du monde réel de l'API Win32 ...
http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx
C'est 12 "dépendances" à gérer. Par exemple, si les résolutions d'écran deviennent vraiment énormes, nous aurons peut-être besoin de valeurs de coordonnées 64 bits - et d'une autre version de CreateWindowEx. Et oui, il y a déjà une ancienne version qui traîne, qui est probablement mappée à la nouvelle version dans les coulisses ...
http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx
Ces "dépendances" ne sont pas seulement un problème pour le développeur d'origine - tous ceux qui utilisent cette interface doivent rechercher quelles sont les dépendances, comment elles sont spécifiées et ce qu'elles signifient, et déterminer ce qu'il faut faire pour leur application. C'est là que les mots «valeurs par défaut sensibles» peuvent rendre la vie beaucoup plus simple.
L'injection de dépendances orientée objet n'est pas différente en principe. L'écriture d'une classe est une surcharge, à la fois dans le texte du code source et dans le temps du développeur, et si cette classe est écrite pour fournir des dépendances selon certaines spécifications d'objets dépendants, alors l'objet dépendant est verrouillé pour prendre en charge cette interface, même s'il y a un besoin pour remplacer l'implémentation de cet objet.
Rien de tout cela ne doit être interprété comme prétendant que l'injection de dépendance est mauvaise - loin de là. Mais toute bonne technique peut être appliquée de manière excessive et au mauvais endroit. Tout comme toutes les chaînes n'ont pas besoin d'être extraites et transformées en paramètre, tous les comportements de bas niveau ne doivent pas être extraits des objets de haut niveau et transformés en une dépendance injectable.
la source
Voici ma propre réaction initiale: Fondamentalement, les mêmes inconvénients de tout modèle.
la source
Le plus gros "inconvénient" de l'inversion de contrôle (pas tout à fait DI, mais assez proche) est qu'il tend à supprimer le fait d'avoir un seul point pour regarder un aperçu d'un algorithme. C'est essentiellement ce qui se produit lorsque vous avez découplé du code - la possibilité de regarder en un seul endroit est un artefact de couplage étroit.
la source
Je ne pense pas qu'une telle liste existe, mais essayez de lire ces articles:
DI peut masquer le code (si vous ne travaillez pas avec un bon IDE)
Une mauvaise utilisation de l'IoC peut conduire à un mauvais code selon l'oncle Bob.
Besoin de faire attention à la sur-ingénierie et de créer une polyvalence inutile.
la source
J'utilise Guice (framework Java DI) de façon intensive depuis 6 mois. Bien que dans l'ensemble, je pense que c'est génial (surtout du point de vue des tests), il y a certains inconvénients. Notamment:
Maintenant que je me suis plaint. Permettez-moi de dire que je continuerai à utiliser (volontiers) Guice dans mon projet actuel et probablement mon prochain. L'injection de dépendance est un modèle génial et incroyablement puissant. Mais cela peut certainement être déroutant et vous passerez presque certainement du temps à maudire le cadre d'injection de dépendance que vous choisissez.
De plus, je suis d'accord avec d'autres affiches sur le fait que l'injection de dépendance peut être surutilisée.
la source
Le code sans DI présente le risque bien connu de s'emmêler dans le code Spaghetti - certains symptômes sont que les classes et les méthodes sont trop grandes, font trop et ne peuvent pas être facilement modifiées, décomposées, refactorisées ou testées.
Le code avec DI utilisé beaucoup peut être du code Ravioli où chaque petite classe est comme une pépite de ravioli individuelle - cela fait une petite chose et le principe de responsabilité unique est respecté, ce qui est bien. Mais en regardant les cours par eux-mêmes, il est difficile de voir ce que fait le système dans son ensemble, car cela dépend de la façon dont toutes ces nombreuses petites pièces s'assemblent, ce qui est difficile à voir. Cela ressemble à un gros tas de petites choses.
En évitant la complexité spaghetti de gros morceaux de code couplé au sein d'une grande classe, vous courez le risque d'un autre type de complexité, où il y a beaucoup de petites classes simples et les interactions entre elles sont complexes.
Je ne pense pas que ce soit un inconvénient fatal - DI est toujours très utile. Un certain degré de style ravioli avec de petites classes qui ne font qu'une chose est probablement bon. Même en excès, je ne pense pas que ce soit mauvais comme code de spaghetti. Mais être conscient qu'il peut être poussé trop loin est la première étape pour l'éviter. Suivez les liens pour discuter de la façon de l'éviter.
la source
Si vous avez une solution maison, les dépendances sont bien visibles dans le constructeur. Ou peut-être comme des paramètres de méthode qui ne sont pas trop difficiles à repérer. Bien que les dépendances gérées par le framework, si elles sont poussées à l'extrême, peuvent commencer à apparaître comme par magie.
Cependant, avoir trop de dépendances dans trop de classes est un signe clair que votre structure de classe est foutue. Donc, d'une certaine manière, l'injection de dépendance (développée à la maison ou gérée par le framework) peut aider à mettre en évidence des problèmes de conception flagrants qui pourraient autrement être cachés dans le noir.
Pour mieux illustrer le deuxième point, voici un extrait de cet article ( source originale ) qui, à mon avis, est le problème fondamental de la construction de tout système, pas seulement des systèmes informatiques.
DI résout-il ce problème? Non . Mais cela vous aide à voir clairement si vous essayez de déléguer la responsabilité de la conception de chaque pièce à ses occupants.
la source
Une chose qui me fait me tortiller un peu avec DI est l'hypothèse que tous les objets injectés sont bon marché pour instancier et ne produisent aucun effet secondaire -OU- la dépendance est utilisée si fréquemment qu'elle l'emporte sur tout coût d'instanciation associé.
Lorsque cela peut être significatif, c'est lorsqu'une dépendance n'est pas fréquemment utilisée au sein d'une classe consommatrice; comme quelque chose comme un
IExceptionLogHandlerService
. De toute évidence, un service comme celui-ci est invoqué (espérons-le :)) rarement dans la classe - sans doute uniquement sur les exceptions devant être enregistrées; mais le modèle d'injection de constructeur canonique ...... exige qu'une instance "en direct" de ce service soit fournie, sacrément les coûts / effets secondaires nécessaires pour y arriver. Non pas que ce serait probablement le cas, mais que se passe-t-il si la construction de cette instance de dépendance implique un accès au service / à la base de données, ou des recherches de fichiers de configuration, ou verrouille une ressource jusqu'à sa suppression? Si ce service était plutôt construit selon les besoins, situé dans le service ou généré en usine (tous ayant des problèmes qui leur sont propres), vous ne prendriez le coût de construction que lorsque cela serait nécessaire.
Maintenant, c'est un principe de conception de logiciel généralement accepté que la construction d'un objet est bon marché et ne produit pas d' effets secondaires. Et bien que ce soit une bonne idée, ce n'est pas toujours le cas. Cependant, l'utilisation d'une injection de constructeur typique exige essentiellement que ce soit le cas. Cela signifie que lorsque vous créez une implémentation d'une dépendance, vous devez la concevoir avec DI à l'esprit. Vous auriez peut-être rendu la construction d'objets plus coûteuse pour obtenir des avantages ailleurs, mais si cette implémentation devait être injectée, elle vous obligerait probablement à reconsidérer cette conception.
Soit dit en passant, certaines techniques peuvent atténuer ce problème exact en permettant le chargement différé des dépendances injectées, par exemple en fournissant à une classe une
Lazy<IService>
instance comme dépendance. Cela changerait les constructeurs de vos objets dépendants et rendrait alors encore plus conscients des détails d'implémentation tels que les dépenses de construction d'objets, ce qui n'est sans doute pas souhaitable non plus.la source
this.errorLogger.WriteError(ex)
lorsqu'une erreur se produit dans une instruction try / catch.L'illusion que vous avez découplé votre code simplement en implémentant l'injection de dépendance sans réellement le découpler. Je pense que c'est la chose la plus dangereuse avec DI.
la source
Il s'agit plus d'un nitpick. Mais l'un des inconvénients de l'injection de dépendances est qu'il est un peu plus difficile pour les outils de développement de raisonner et de naviguer dans le code.
Plus précisément, si vous contrôlez-cliquez / commande-cliquez sur une invocation de méthode dans le code, cela vous amènera à la déclaration de méthode sur une interface au lieu de l'implémentation concrète.
C'est vraiment plus un inconvénient du code à couplage lâche (code conçu par l'interface), et s'applique même si vous n'utilisez pas l'injection de dépendance (c'est-à-dire, même si vous utilisez simplement des usines). Mais l'avènement de l'injection de dépendances est ce qui a vraiment encouragé le code faiblement couplé aux masses, alors j'ai pensé le mentionner.
En outre, les avantages du code faiblement couplé l'emportent largement sur cela, donc je l'appelle un nitpick. Bien que j'aie travaillé assez longtemps pour savoir que c'est le genre de refoulement que vous obtiendrez si vous essayez d'introduire l'injection de dépendance.
En fait, je me risquerais à deviner que pour chaque "inconvénient" que vous pouvez trouver pour l'injection de dépendance, vous trouverez de nombreux avantages qui l'emportent de loin.
la source
L' injection de dépendances basée sur le constructeur (sans l'aide de "frameworks" magiques) est un moyen propre et avantageux de structurer le code OO. Dans les meilleures bases de code que j'ai vues, au fil des années passées avec d'autres anciens collègues de Martin Fowler, j'ai commencé à remarquer que la plupart des bonnes classes écrites de cette façon finissent par avoir une seule
doSomething
méthode.L'inconvénient majeur, alors, est qu'une fois que vous vous rendez compte que tout n'est qu'une manière OO fastidieuse d'écrire des fermetures sous forme de classes afin d'obtenir les avantages de la programmation fonctionnelle, votre motivation à écrire du code OO peut rapidement s'évaporer.
la source
Je trouve que l'injection de constructeur peut conduire à de gros constructeurs moches (et je l'utilise dans toute ma base de code - peut-être que mes objets sont trop granulaires?). De plus, parfois avec l'injection de constructeur, je me retrouve avec des dépendances circulaires horribles (bien que ce soit très rare), vous pouvez donc vous retrouver devant avoir une sorte de cycle de vie d'état prêt avec plusieurs cycles d'injection de dépendance dans un système plus complexe.
Cependant, je préfère l'injection de construction à l'injection de setter car une fois que mon objet est construit, je sais sans aucun doute dans quel état il se trouve, que ce soit dans un environnement de test unitaire ou chargé dans un conteneur IOC. Ce qui, d'une manière détournée, dit ce que je pense être le principal inconvénient de l'injection de setter.
(En tant que sidenote, je trouve tout le sujet assez "religieux", mais votre kilométrage variera en fonction du niveau de fanatisme technique de votre équipe de développement!)
la source
Si vous utilisez DI sans conteneur IOC, le plus gros inconvénient est que vous voyez rapidement combien de dépendances votre code a réellement et comment tout est étroitement couplé. ("Mais je pensais que c'était un bon design!") La progression naturelle est de se diriger vers un conteneur IOC qui peut prendre un peu de temps pour apprendre et mettre en œuvre (pas aussi mauvais que la courbe d'apprentissage WPF, mais ce n'est pas gratuit Soit). Le dernier inconvénient est que certains développeurs commenceront à écrire des tests unitaires honnêtes et bons et il leur faudra du temps pour le comprendre. Les développeurs qui pouvaient auparavant lancer quelque chose en une demi-journée passeront soudainement deux jours à essayer de se moquer de toutes leurs dépendances.
Semblable à la réponse de Mark Seemann, l'essentiel est que vous passez du temps à devenir un meilleur développeur plutôt que de pirater des morceaux de code ensemble et de le lancer à la porte / en production. Quelle serait votre entreprise plutôt? Vous seul pouvez y répondre.
la source
DI est une technique ou un modèle et n'est lié à aucun cadre. Vous pouvez câbler vos dépendances manuellement. DI vous aide avec SR (responsabilité unique) et SoC (séparation des préoccupations). DI conduit à une meilleure conception. De mon point de vue et de mon expérience, il n'y a pas d'inconvénients . Comme avec tout autre modèle, vous pouvez vous tromper ou en abuser (mais ce qui est assez difficile dans le cas de DI).
Si vous introduisez DI comme principe dans une application héritée, en utilisant un framework - la plus grosse erreur que vous puissiez faire est de l'utiliser à mauvais escient en tant que Service-Locater. DI + Framework lui-même est génial et a juste amélioré les choses partout où je l'ai vu! Du point de vue organisationnel, il y a les problèmes communs à chaque nouveau processus, technique, modèle, ...:
En général, vous devez investir du temps et de l'argent , à côté de cela, il n'y a pas d'inconvénients, vraiment!
la source
Lisibilité du code. Vous ne pourrez pas facilement comprendre le flux de code car les dépendances sont masquées dans les fichiers XML.
la source
Deux choses:
Par exemple, IntelliJ (édition commerciale) prend en charge la vérification de la validité d'une configuration Spring et signalera les erreurs telles que les violations de type dans la configuration. Sans ce type de prise en charge d'outils, vous ne pouvez pas vérifier si la configuration est valide avant d'exécuter des tests.
C'est une des raisons pour lesquelles le modèle de «gâteau» (comme il est connu de la communauté Scala) est une bonne idée: le câblage entre les composants peut être vérifié par le vérificateur de type. Vous n'avez pas cet avantage avec les annotations ou XML.
Des cadres tels que Spring ou Guice rendent difficile la détermination statique de l'apparence du graphique d'objet créé par le conteneur. Bien qu'ils créent un graphique d'objet au démarrage du conteneur, ils ne fournissent pas d'API utiles qui décrivent le graphique d'objet qui / serait / serait créé.
la source
Il semble que les avantages supposés d'un langage à typage statique diminuent considérablement lorsque vous utilisez constamment des techniques pour contourner le typage statique. Une grande boutique Java que je viens d'interviewer a cartographié leurs dépendances de construction avec une analyse de code statique ... qui devait analyser tous les fichiers Spring pour être efficace.
la source
Cela peut augmenter le temps de démarrage de l'application, car le conteneur IoC doit résoudre les dépendances de manière appropriée et nécessite parfois plusieurs itérations.
la source