Je cherche à implémenter l'injection de dépendances dans une application relativement grande, mais je n'ai aucune expérience en la matière. J'ai étudié le concept et quelques implémentations d'IoC et d'injecteurs de dépendances disponibles, comme Unity et Ninject. Cependant, il y a une chose qui m'échappe. Comment dois-je organiser la création d'instances dans mon application?
Ce à quoi je pense, c'est que je peux créer quelques usines spécifiques qui contiendront la logique de création d'objets pour quelques types de classes spécifiques. Fondamentalement, une classe statique avec une méthode appelant la méthode Ninject Get () d'une instance de noyau statique dans cette classe.
Sera-ce une approche correcte de l'implémentation de l'injection de dépendances dans mon application ou devrais-je l'implémenter selon un autre principe?
la source
Réponses:
Ne pensez pas encore à l'outil que vous allez utiliser. Vous pouvez faire DI sans conteneur IoC.
Premier point: Mark Seemann a un très bon livre sur DI dans .Net
Deuxième: racine de composition. Assurez-vous que toute la configuration se fait au point d'entrée du projet. Le reste de votre code doit connaître les injections, pas les outils utilisés.
Troisièmement: l'injection de constructeur est la voie la plus probable (il y a des cas où vous ne le voudriez pas, mais pas beaucoup).
Quatrièmement: étudier l'utilisation des usines lambda et d'autres fonctionnalités similaires pour éviter de créer des interfaces / classes inutiles dans le seul but de l'injection.
la source
Votre question comporte deux parties: comment implémenter DI correctement et comment refactoriser une grande application pour utiliser DI.
La première partie est bien répondue par @Miyamoto Akira (en particulier la recommandation de lire le livre de Mark Seemann sur "l'injection de dépendance dans .net". Le blog Marks est également une bonne ressource gratuite.
La deuxième partie est beaucoup plus compliquée.
Une bonne première étape serait de simplement déplacer toute l'instanciation dans les constructeurs de classes - pas d'injecter les dépendances, juste de vous assurer que vous n'appelez que
new
le constructeur.Cela mettra en évidence toutes les violations de SRP que vous avez commises, afin que vous puissiez commencer à diviser la classe en petits collaborateurs.
Le prochain problème que vous trouverez sera les classes qui dépendent des paramètres d'exécution pour la construction. Vous pouvez généralement résoudre ce problème en créant des usines simples, souvent avec
Func<param,type>
, en les initialisant dans le constructeur et en les appelant dans les méthodes.La prochaine étape serait de créer des interfaces pour vos dépendances et d'ajouter un deuxième constructeur à vos classes à l'exception de ces interfaces. Votre constructeur sans paramètre renouvelle les instances concrètes et les transmet au nouveau constructeur. Ceci est communément appelé «B * stard Injection» ou «Poor mans DI».
Cela vous donnera la possibilité de faire des tests unitaires, et si c'était le principal objectif du refactoriste, c'est peut-être là que vous vous arrêtez. Le nouveau code sera écrit avec l'injection de constructeur, mais votre ancien code peut continuer à fonctionner tel qu'il est écrit mais toujours testable.
Vous pouvez bien sûr aller plus loin. Si vous avez l'intention d'utiliser un conteneur IOC, alors une étape suivante pourrait être de remplacer tous les appels directs à
new
dans vos constructeurs sans paramètres par des appels statiques au conteneur IOC, essentiellement (ab) en l'utilisant comme localisateur de service.Cela générera plus de cas de paramètres de constructeur d'exécution à traiter comme auparavant.
Une fois cela fait, vous pouvez commencer à supprimer les constructeurs sans paramètres et à refactoriser la DI pure.
En fin de compte, cela va être beaucoup de travail, alors assurez-vous de décider pourquoi vous voulez le faire, et priorisez les parties de la base de code qui bénéficieront le plus du refactor.
la source
Tout d'abord, je tiens à mentionner que vous vous rendez la tâche beaucoup plus difficile en refactorisant un projet existant plutôt qu'en démarrant un nouveau projet.
Vous avez dit qu'il s'agissait d'une grande application, alors choisissez un petit composant pour commencer. De préférence un composant «nœud-feuille» qui n'est utilisé par rien d'autre. Je ne sais pas quel est l'état des tests automatisés sur cette application, mais vous casserez tous les tests unitaires pour ce composant. Alors préparez-vous à cela. L'étape 0 consiste à écrire des tests d'intégration pour le composant que vous modifierez s'ils n'existent pas déjà. En dernier recours (pas d'infrastructure de test; pas d'adhésion pour l'écrire), déterminez une série de tests manuels que vous pouvez faire pour vérifier que ce composant fonctionne.
La façon la plus simple de définir votre objectif pour le refactoriseur DI est de supprimer toutes les instances du "nouvel" opérateur de ce composant. Ceux-ci se répartissent généralement en deux catégories:
Variable membre invariante: ce sont des variables qui sont définies une fois (généralement dans le constructeur) et ne sont pas réaffectées pour la durée de vie de l'objet. Pour ceux-ci, vous pouvez injecter une instance de l'objet dans le constructeur. Vous n'êtes généralement pas responsable de l'élimination de ces objets (je ne veux pas dire jamais ici, mais vous ne devriez vraiment pas avoir cette responsabilité).
Variable membre variable / méthode variable: Ce sont des variables qui récupèreront les ordures à un moment donné pendant la durée de vie de l'objet. Pour ceux-ci, vous voudrez injecter une usine dans votre classe pour fournir ces instances. Vous êtes responsable de l'élimination des objets créés par une usine.
Votre conteneur IoC (ninject on dirait) prendra la responsabilité d'instancier ces objets et de mettre en œuvre vos interfaces d'usine. Tout ce qui utilise le composant que vous avez modifié devra connaître le conteneur IoC pour qu'il puisse récupérer votre composant.
Une fois que vous avez terminé ce qui précède, vous pourrez profiter de tous les avantages que vous espérez obtenir de DI dans votre composant sélectionné. Ce serait le bon moment pour ajouter / corriger ces tests unitaires. S'il y avait des tests unitaires existants, vous devrez décider si vous voulez les patcher ensemble en injectant des objets réels ou écrire de nouveaux tests unitaires à l'aide de maquettes.
'Simplement', répétez ce qui précède pour chaque composant de votre application, en déplaçant la référence vers le conteneur IoC au fur et à mesure jusqu'à ce que seul le principal ait besoin de le savoir.
la source
L'approche correcte consiste à utiliser l'injection de constructeur, si vous utilisez
alors vous vous retrouvez avec localisateur de service, que l'injection de dépendance.
la source
Vous dites que vous voulez l'utiliser mais ne dites pas pourquoi.
DI n'est rien d'autre que fournir un mécanisme pour générer des concrétions à partir d'interfaces.
Cela vient en soi du DIP . Si votre code est déjà écrit dans ce style et que vous avez un seul endroit où des concrétions sont générées, DI n'apporte plus rien à la fête. L'ajout de code de cadre DI ici serait tout simplement gonflé et obscurcir votre base de code.
En supposant que vous ne voulez l'utiliser, vous généralement mis en place l'usine / constructeur / conteneur (ou autre) au début de l'application il est clairement visible.
NB il est très facile de rouler le vôtre si vous le souhaitez plutôt que de vous engager sur Ninject / StructureMap ou autre. Si toutefois vous avez un roulement raisonnable de personnel, il peut graisser les roues pour utiliser un cadre reconnu ou au moins l'écrire dans ce style afin qu'il ne soit pas trop une courbe d'apprentissage.
la source
En fait, la "bonne" façon est de ne PAS utiliser d'usine du tout, sauf s'il n'y a absolument pas d'autre choix (comme dans les tests unitaires et certaines simulations - pour le code de production, vous n'utilisez PAS d'usine)! Cela est en fait un anti-modèle et doit être évité à tout prix. Tout l'intérêt d'un conteneur DI est de permettre au gadget de faire le travail pour vous.
Comme indiqué ci-dessus dans un article précédent, vous voulez que votre gadget IoC assume la responsabilité de la création des différents objets dépendants dans votre application. Cela signifie laisser votre gadget DI créer et gérer les différentes instances lui-même. C'est tout le point derrière DI - vos objets ne doivent JAMAIS savoir comment créer et / ou gérer les objets dont ils dépendent. Pour faire autrement pauses lâche accouplement.
La conversion d'une application existante en toutes DI est une étape énorme, mais en mettant de côté les difficultés évidentes à le faire, vous voudrez également (juste pour vous faciliter la vie) explorer un outil DI qui exécutera automatiquement la majeure partie de vos liaisons (le noyau de quelque chose comme Ninject est les
"kernel.Bind<someInterface>().To<someConcreteClass>()"
appels que vous faites pour faire correspondre vos déclarations d'interface aux classes concrètes que vous souhaitez utiliser pour implémenter ces interfaces. Ce sont ces appels "Bind" qui permettent à votre gadget DI d'intercepter vos appels de constructeur et de fournir les instances d'objets dépendantes nécessaires. Un constructeur typique (pseudo-code montré ici) pour une classe peut être:Notez que nulle part dans ce code, aucun code n'a créé / géré / publié l'instance de SomeConcreteClassA ou SomeOtherConcreteClassB. En fait, aucune classe concrète n'était même référencée. Alors ... où la magie s'est-elle produite?
Dans la partie de démarrage de votre application, les événements suivants ont eu lieu (encore une fois, il s'agit d'un pseudo-code mais il est assez proche de la réalité (Ninject) ...):
Ce petit bout de code indique au gadget Ninject de rechercher des constructeurs, de les analyser, de rechercher des instances d'interfaces qu'il a été configuré pour gérer (c'est-à-dire les appels "Bind"), puis de créer et de remplacer une instance de la classe concrète partout où l'instance est référencée.
Il existe un bel outil qui complète très bien Ninject, appelé Ninject.Extensions.Conventions (encore un autre package NuGet) qui fera le gros de ce travail pour vous. Ne pas retirer de l'excellente expérience d'apprentissage que vous vivrez au fur et à mesure que vous la construirez, mais pour vous lancer, cela pourrait être un outil pour enquêter.
Si la mémoire est bonne, Unity (officiellement de Microsoft maintenant un projet Open Source) a un appel de méthode ou deux qui font la même chose, d'autres outils ont des assistants similaires.
Quelle que soit la voie que vous choisissez, lisez certainement le livre de Mark Seemann pour l'essentiel de votre formation en DI, cependant, il convient de souligner que même les «grands» du monde de l'ingénierie logicielle (comme Mark) peuvent faire des erreurs flagrantes - Mark a tout oublié Ninject dans son livre alors voici une autre ressource écrite juste pour Ninject. Je l'ai et c'est une bonne lecture: Mastering Ninject for Dependency Injection
la source
Il n'y a pas de «bonne façon», mais il y a quelques principes simples à suivre:
C'est tout. Bien sûr, ce sont des principes et non des lois, mais si vous les suivez, vous pouvez être sûr que vous faites DI (veuillez me corriger si je me trompe).
Alors, comment créer des objets en cours d'exécution sans "nouveau" et sans connaître le conteneur DI?
Dans le cas de NInject, il existe une extension d'usine qui permet la création d'usines. Bien sûr, les usines créées ont toujours une référence interne au noyau, mais celle-ci n'est pas accessible depuis votre application.
la source