Je comprends que l’instanciation directe des dépendances au sein d’une classe est considérée comme une mauvaise pratique. Cela a du sens car cela permet de coupler étroitement tout ce qui rend les tests très difficiles.
Presque tous les frameworks que j'ai rencontrés semblent préférer l'injection de dépendance avec un conteneur à l'utilisation de localisateurs de services. Les deux semblent obtenir le même résultat en permettant au programmeur de spécifier quel objet doit être renvoyé lorsqu'une classe nécessite une dépendance.
Quelle est la différence entre les deux? Pourquoi devrais-je choisir l'un sur l'autre?
Réponses:
Lorsque l'objet lui-même est responsable de demander ses dépendances, par opposition à les accepter via un constructeur, il cache certaines informations essentielles. Ce n'est que légèrement meilleur que le cas très étroitement lié d'utilisation
new
d'instancier ses dépendances. Il réduit le couplage car vous pouvez en fait changer les dépendances qu'il génère, mais il a toujours une dépendance qu'il ne peut pas ébranler: le localisateur de service. Cela devient la chose dont tout dépend.Un conteneur qui fournit des dépendances via des arguments de constructeur apporte le plus de clarté. Nous voyons dès le départ qu'un objet a besoin d'un
AccountRepository
et d'unPasswordStrengthEvaluator
. Lorsque vous utilisez un service de localisation, cette information est moins apparente immédiatement. Vous voyez tout de suite le cas d'un objet qui a, oh, 17 dépendances, et vous dites: "Hmm, cela semble être beaucoup. Que se passe-t-il là-dedans?" Les appels à un localisateur de service peuvent être répartis autour des différentes méthodes et cachés derrière une logique conditionnelle. Vous ne réaliserez peut-être pas que vous avez créé une "classe de Dieu" - une classe qui fait tout. Peut-être que cette classe pourrait être refactorisée en 3 classes plus petites, plus ciblées et donc plus testables.Considérons maintenant les tests. Si un objet utilise un localisateur de service pour obtenir ses dépendances, votre infrastructure de test aura également besoin d'un localisateur de service. Dans un test, vous allez configurer le localisateur de service pour fournir les dépendances à l'objet à tester (peut-être a
FakeAccountRepository
et a)VeryForgivingPasswordStrengthEvaluator
, puis exécuter le test. Mais c'est plus de travail que de spécifier des dépendances dans le constructeur de l'objet. Et votre infrastructure de test devient également dépendante du localisateur de services. C'est une autre chose que vous devez configurer dans chaque test, ce qui rend l'écriture de tests moins attrayante.Cherchez "Serivce Locator est un anti-modèle" pour l'article de Mark Seeman à ce sujet. Si vous êtes dans le monde .Net, procurez-vous son livre. C'est très bien.
la source
constructor supplied dependencies
contreservice locator
est que l'ancien peut être verfied compilation, alors que celui - ci ne peut que être l' exécution vérifiée.But that's more work than specifying dependencies in the object's constructor.
je voudrais objecter. Avec un localisateur de service, il vous suffit de spécifier les 3 dépendances dont vous avez réellement besoin pour votre test. Avec une DI basée sur le constructeur, vous devez spécifier TOUTES LES 10, même si 7 ne sont pas utilisées.Imaginez que vous soyez un ouvrier dans une usine de chaussures .
Vous êtes responsable de l' assemblage des chaussures et vous aurez donc besoin de beaucoup de choses pour le faire.
Etc.
Vous êtes au travail dans l'usine et vous êtes prêt à commencer. Vous avez une liste d'instructions sur la façon de procéder, mais vous ne disposez pas encore du matériel ou des outils.
Un localisateur de service est comme un contremaître qui peut vous aider à obtenir ce dont vous avez besoin.
Vous demandez au localisateur de services chaque fois que vous avez besoin de quelque chose, et ils partent à votre recherche. Le localisateur de services a été informé à l'avance de ce que vous êtes susceptible de demander et comment le trouver.
Vous feriez mieux d'espérer que vous ne demandiez pas quelque chose d'inattendu cependant. Si le localisateur n'a pas été informé à l'avance d'un outil ou d'un matériau en particulier, il ne pourra pas l'obtenir pour vous et il vous haussera les épaules.
Un conteneur d'injection de dépendance (DI) est comme une grande boîte qui contient tout ce dont tout le monde a besoin au début de la journée.
Lorsque l’usine démarre, le Big Boss, connu sous le nom de Composition Root, saisit le conteneur et donne tout le contenu aux responsables de ligne .
Les responsables hiérarchiques ont maintenant ce dont ils ont besoin pour s’acquitter de leurs tâches de la journée. Ils prennent ce qu'ils ont et transmettent ce qui est nécessaire à leurs subordonnés.
Ce processus se poursuit, les dépendances se répercutant sur la chaîne de production. Finalement, un conteneur de matériaux et d'outils apparaît pour votre contremaître.
Votre contremaître distribue maintenant exactement ce dont vous avez besoin, à vous et aux autres travailleurs, sans même que vous le demandiez.
En gros, dès que vous vous présentez au travail, tout ce dont vous avez besoin se trouve déjà dans une boîte qui vous attend. Vous n'avez besoin de rien savoir sur la façon de les obtenir.
la source
Quelques points supplémentaires que j'ai trouvés en parcourant le Web:
la source
J'arrive en retard à cette soirée mais je ne peux pas résister.
Parfois pas du tout. Ce qui fait la différence, c'est ce qui sait de quoi.
Vous savez que vous utilisez un localisateur de service lorsque le client à la recherche de la dépendance connaît le conteneur. Un client sachant trouver ses dépendances, même lorsqu'il passe par un conteneur pour les obtenir, est le modèle de localisation de service.
Cela signifie-t-il que si vous souhaitez éviter le service de localisation, vous ne pouvez pas utiliser un conteneur? Non, vous devez juste empêcher les clients de connaître le conteneur. La principale différence réside dans l’utilisation du conteneur.
Permet de dire les
Client
besoinsDependency
. Le conteneur a unDependency
.Nous venons de suivre le modèle de localisateur de service car
Client
sait comment le trouverDependency
. Bien sûr, il utilise un code dur,ClassPathXmlApplicationContext
mais même si vous injectez que vous avez toujours un localisateur de service parce que desClient
appelsbeanfactory.getBean()
.Pour éviter le service de localisation, vous n'avez pas à abandonner ce conteneur. Vous devez juste le sortir
Client
afin deClient
ne pas le savoir.Remarquez comment
Client
maintenant n’a aucune idée que le conteneur existe:Déplacez le conteneur de tous les clients et collez-le dans le répertoire principal où il peut créer un graphe d'objets de tous vos objets de longue durée. Choisissez l'un de ces objets à extraire, appelez une méthode dessus et commencez à compter tout le graphique.
Cela déplace toute la construction statique dans les conteneurs XML tout en gardant tous vos clients parfaitement ignorants de la façon de trouver leurs dépendances.
Mais principal sait toujours comment localiser les dépendances! Oui. Mais en ne diffusant pas ces informations, vous avez évité le problème fondamental du localisateur de services. La décision d'utiliser un conteneur est maintenant prise à un endroit et peut être modifiée sans réécrire des centaines de clients.
la source
Je pense que la meilleure façon de comprendre la différence entre les deux et la raison pour laquelle un conteneur DI est tellement préférable à un localisateur de service est de réfléchir à la raison pour laquelle nous faisons l'inversion de dépendance.
Nous faisons l'inversion de dépendance de sorte que chaque classe énonce explicitement de quoi elle dépend pour son fonctionnement. Nous le faisons parce que cela crée le couplage le plus lâche possible. Plus le couplage est lâche, plus il est facile de tester et de refactoriser (et nécessite généralement le moins de refactorisation à l'avenir car le code est plus propre).
Regardons la classe suivante:
Dans cette classe, nous déclarons explicitement qu'il nous faut un IOutputProvider et rien d'autre pour que cette classe fonctionne. Ceci est entièrement testable et dépend d'une interface unique. Je peux déplacer cette classe n'importe où dans mon application, y compris un projet différent. Tout ce dont elle a besoin est un accès à l'interface IOutputProvider. Si d'autres développeurs souhaitent ajouter quelque chose de nouveau à cette classe, qui nécessite une deuxième dépendance, ils doivent être explicites sur ce dont ils ont besoin dans le constructeur.
Regardez la même classe avec un localisateur de services:
Maintenant, j'ai ajouté le localisateur de service en tant que dépendance. Voici les problèmes qui sont immédiatement évidents:
Alors, pourquoi ne faisons-nous pas du localisateur de services une classe statique? Nous allons jeter un coup d'oeil:
C'est beaucoup plus simple, non?
Faux.
Supposons que IOutputProvider soit implémenté par un service Web très long qui écrit la chaîne dans quinze bases de données différentes dans le monde entier et prend beaucoup de temps.
Essayons de tester cette classe. Nous avons besoin d'une implémentation différente de IOutputProvider pour le test. Comment écrivons-nous le test?
Pour ce faire, nous devons procéder à une configuration sophistiquée dans la classe statique ServiceLocator afin d’utiliser une implémentation différente de IOutputProvider lorsqu’elle est appelée par le test. Même écrire cette phrase était douloureux. Sa mise en œuvre serait tortueuse et constituerait un cauchemar de maintenance . Nous ne devrions jamais avoir besoin de modifier une classe spécifiquement pour les tests, surtout si cette classe n'est pas la classe que nous essayons réellement de tester.
Alors maintenant, il vous reste soit a) un test qui provoque des modifications de code intrusives dans la classe ServiceLocator non liée; ou b) pas de test du tout. Et vous vous retrouvez également avec une solution moins flexible.
Donc, la classe de localisateur de service doit être injectée dans le constructeur. Ce qui signifie que nous nous retrouvons avec les problèmes spécifiques mentionnés plus haut. Le localisateur de service nécessite plus de code, indique aux autres développeurs qu'il a besoin de choses inutiles, encourage les autres développeurs à écrire du code moins performant et nous offre moins de flexibilité pour aller de l'avant.
En termes simples, les localisateurs de services augmentent le couplage dans une application et encouragent les autres développeurs à écrire du code hautement couplé .
la source