Quelle est la différence entre les modèles d'Injection de dépendances et Localisateur de services?


304

Les deux modèles semblent être une mise en œuvre du principe d'inversion de contrôle. Autrement dit, un objet ne doit pas savoir comment construire ses dépendances.

L'injection de dépendance (DI) semble utiliser un constructeur ou un setter pour «injecter» ses dépendances.

Exemple d'utilisation de l'injection de constructeur:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Le localisateur de service semble utiliser un "conteneur", qui connecte ses dépendances et donne foo c'est bar.

Exemple d'utilisation d'un localisateur de service:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}

Parce que nos dépendances ne sont que des objets eux-mêmes, ces dépendances ont des dépendances, qui ont encore plus de dépendances, et ainsi de suite. Ainsi, l'Inversion of Control Container (ou DI Container) est né. Exemples: Castle Windsor, Ninject, Structure Map, Spring, etc.)

Mais un conteneur IOC / DI ressemble exactement à un localisateur de service. L'appeler un conteneur DI est-il un mauvais nom? Un conteneur IOC / DI n'est-il qu'un autre type de localisateur de service? La nuance réside-t-elle dans le fait que nous utilisons les conteneurs DI principalement lorsque nous avons de nombreuses dépendances?


13
L'inversion du contrôle signifie qu '"un objet ne doit pas savoir construire ses dépendances"?!? Celui-là est nouveau pour moi. Non, vraiment, ce n'est pas ce que signifie "inversion de contrôle". Voir martinfowler.com/bliki/InversionOfControl.html Cet article fournit même des références pour l'étymologie du terme, qui remonte aux années 1980.
Rogério


1
Mark Seemann soutient que Service Locator est anti-modèle ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). En outre, j'ai trouvé le diagramme (trouvé ici, stackoverflow.com/a/9503612/1977871 ) utile pour comprendre la situation difficile DI et SL. J'espère que cela t'aides.
VivekDev

Réponses:


181

La différence peut sembler légère, mais même avec ServiceLocator, la classe est toujours responsable de la création de ses dépendances. Il utilise simplement le localisateur de services pour le faire. Avec DI, la classe reçoit ses dépendances. Il ne sait ni ne se soucie d'où ils viennent. Un résultat important de cela est que l'exemple DI est beaucoup plus facile à tester unitaire - car vous pouvez lui passer des implémentations simulées de ses objets dépendants. Vous pouvez combiner les deux - et injecter le localisateur de service (ou une usine), si vous le souhaitez.


20
De plus, vous pouvez utiliser les deux lors de la construction d'une classe. Le constructeur par défaut peut utiliser le SL pour récupérer les dépendances et les transmettre au constructeur "réel" qui reçoit ces dépendances. Tu reçois le meilleur des deux mondes.
Grant Palin

6
Non, le ServiceLocator est le responsable de l'instanciation de l'implémentation correcte pour une dépendance donnée (plugin). Dans le cas de DI, c'est le "conteneur" DI qui en est responsable.
Rogério

5
@Rogerio oui mais la classe doit encore connaître le Service Locator ... c'est deux dépendances. Plus souvent qu'autrement, j'ai vu le localisateur de services déléguer au conteneur DI pour la recherche, en particulier pour les objets transitoires qui ont besoin d'une assistance technique.
Adam Gent

2
@Adam Je n'ai pas dit que le localisateur de service déléguerait à un conteneur DI. Ce sont deux modèles mutuellement exclusifs, comme décrit dans l'article "officiel" . Pour moi, Service Locator a un énorme avantage sur DI dans la pratique: l'utilisation d'un conteneur DI invite à des abus (ce que j'ai vu à plusieurs reprises), contrairement à l'utilisation d'un Service Locator.
Rogério

3
"Un résultat important de cela est que l'exemple DI est beaucoup plus facile à tester unitaire - car vous pouvez lui passer des implémentations simulées de ses objets dépendants." Pas vrai. Dans vos tests unitaires, un appel à une fonction de registre dans le conteneur du localisateur de service peut être utilisé pour ajouter facilement des simulations au registre.
Drumbeg

93

Lorsque vous utilisez un localisateur de services, chaque classe dépendra de votre localisateur de services. Ce n'est pas le cas avec l'injection de dépendance. L'injecteur de dépendances sera généralement appelé une seule fois au démarrage pour injecter des dépendances dans une classe principale. Les classes dont dépend cette classe principale verront récursivement leurs dépendances injectées, jusqu'à ce que vous ayez un graphique d'objet complet.

Une bonne comparaison: http://martinfowler.com/articles/injection.html

Si votre injecteur de dépendances ressemble à un localisateur de services, où les classes appellent directement l'injecteur, ce n'est probablement pas un injecteur de dépendances, mais plutôt un localisateur de services.


17
Mais comment gérez-vous le cas où vous devez créer des objets pendant l'exécution? Si vous les créez manuellement avec "nouveau", vous ne pouvez pas utiliser DI. Si vous appelez le framework DI pour obtenir de l'aide, vous cassez le modèle. Alors, quelles options restent?
Boris

9
@Boris J'ai eu le même problème et j'ai décidé d'injecter des usines spécifiques aux classes. Pas joli mais a fait le travail. J'adorerais voir une solution plus jolie.
Charlie Rudenstål

Lien direct vers la comparaison: martinfowler.com/articles/…
Teoman shipahi

2
@Boris Si j'avais besoin de construire de nouveaux objets à la volée, j'injecterais une usine abstraite pour lesdits objets. Ce qui serait similaire à l'injection d'un localisateur de service dans ce cas, mais fournit une interface concrète, uniforme, au moment de la compilation, pour construire les objets pertinents et rend les dépendances explicites.
LivePastTheEnd

51

Les localisateurs de service masquent les dépendances - vous ne pouvez pas dire en regardant un objet s'il atteint une base de données ou non (par exemple) lorsqu'il obtient des connexions à partir d'un localisateur. Avec l'injection de dépendances (au moins l'injection de constructeur), les dépendances sont explicites.

De plus, les localisateurs de service interrompent l'encapsulation car ils fournissent un point d'accès global aux dépendances d'autres objets. Avec le localisateur de service, comme avec tout singleton :

il devient difficile de spécifier les conditions de pré et de post pour l'interface de l'objet client, car le fonctionnement de son implémentation peut être perturbé de l'extérieur.

Avec l'injection de dépendances, une fois les dépendances d'un objet spécifiées, elles sont sous le contrôle de l'objet lui-même.


3
Je préfère "Singletons considérés comme stupides", steve.yegge.googlepages.com/singleton-considered-stupid
Charles Graham

2
J'aime ol 'Steve Yegge et le titre de cet article est génial, mais je pense que l'article que j'ai cité et les "Singletons sont des menteurs pathologiques" de Miško Hevery ( misko.hevery.com/2008/08/17/singletons-are-pathological- menteurs ) plaident mieux contre la diabolique particulière du localisateur de services.
Jeff Sternal

Cette réponse est la plus correcte car elle définit le mieux un localisateur de service: "Une classe qui cache ses dépendances." Notez que la création d'une dépendance en interne, bien que souvent pas une bonne chose, ne fait pas d'une classe un localisateur de service. En outre, prendre une dépendance sur un conteneur est un problème mais pas "le" problème qui définit le plus clairement un localisateur de service.
Sam

1
With dependency injection (at least constructor injection) the dependencies are explicit.. S'il vous plaît, expliquez.
FindOutIslamNow

Comme ci-dessus, je ne vois pas comment SL rend les dépendances moins explicites que DI ...
Michał Powłoka

38

Martin Fowler déclare :

Avec le localisateur de services, la classe d'application le demande explicitement par un message au localisateur. Avec l'injection, il n'y a pas de demande explicite, le service apparaît dans la classe d'application - d'où l'inversion du contrôle.

En bref: Service Locator et Dependency Injection ne sont que des implémentations du Dependency Inversion Principle.

Le principe important est «Dépendre des abstractions, pas des concrétions». Cela rendra la conception de votre logiciel «faiblement couplée», «extensible», «flexible».

Vous pouvez utiliser celui qui correspond le mieux à vos besoins. Pour une grande application, ayant une énorme base de code, vous feriez mieux d'utiliser un localisateur de service, car l'injection de dépendances nécessiterait plus de modifications dans votre base de code.

Vous pouvez consulter cet article: Inversion de dépendance: Localisateur de service ou Injection de dépendance

Aussi le classique: l' inversion des conteneurs de contrôle et le modèle d'injection de dépendance par Martin Fowler

Concevoir des classes réutilisables par Ralph E. Johnson & Brian Foote

Cependant, celui qui m'a ouvert les yeux était: ASP.NET MVC: résoudre ou injecter? C'est ça le problème… par Dino Esposito


Résumé fantastique: "Localisateur de services et injection de dépendance ne sont que des implémentations du principe d'inversion de dépendance."
Hans

Et il déclare également: La principale différence est qu'avec un localisateur de service, chaque utilisateur d'un service a une dépendance vis-à-vis du localisateur. Le localisateur peut masquer les dépendances à d'autres implémentations, mais vous devez voir le localisateur. La décision entre le localisateur et l'injecteur dépend donc de la question de savoir si cette dépendance est un problème.
programaths

1
ServiceLocator et DI n'ont rien à voir avec le "principe d'inversion de dépendance" (DIP). DIP est un moyen de rendre un composant de haut niveau plus réutilisable, en remplaçant une dépendance au moment de la compilation sur un composant de bas niveau par une dépendance sur un type abstrait défini avec le composant de haut niveau, qui est mis en œuvre par le composante de niveau; de cette façon, la dépendance au moment de la compilation est inversée, car c'est maintenant celle de bas niveau qui dépend de celle de haut niveau. Notez également que l'article de Martin Fowler explique que DI et IoC ne sont pas la même chose.
Rogério

23

Une classe utilisant le constructeur DI indique pour consommer du code qu'il y a des dépendances à satisfaire. Si la classe utilise le SL en interne pour récupérer ces dépendances, le code consommateur n'est pas au courant des dépendances. Cela peut sembler mieux en surface, mais il est en fait utile de connaître les dépendances explicites. C'est mieux d'un point de vue architectural. Et lorsque vous effectuez des tests, vous devez savoir si une classe a besoin de certaines dépendances et configurer la SL pour fournir de fausses versions appropriées de ces dépendances. Avec DI, passez simplement les faux. Pas une énorme différence, mais elle est là.

DI et SL peuvent cependant fonctionner ensemble. Il est utile d'avoir un emplacement central pour les dépendances courantes (par exemple les paramètres, l'enregistreur, etc.). Étant donné une classe utilisant de tels deps, vous pouvez créer un "vrai" constructeur qui reçoit les deps, et un constructeur par défaut (sans paramètre) qui récupère de la SL et transmet au "vrai" constructeur.

EDIT: et, bien sûr, lorsque vous utilisez le SL, vous introduisez un couplage à ce composant. Ce qui est ironique, car l'idée d'une telle fonctionnalité est d'encourager les abstractions et de réduire le couplage. Les préoccupations peuvent être équilibrées et cela dépend du nombre d'emplacements dont vous auriez besoin pour utiliser le SL. Si cela est fait comme suggéré ci-dessus, juste dans le constructeur de classe par défaut.


Intéressant! J'utilise à la fois DI et SL, mais pas avec deux constructeurs. Trois ou quatre dépendances les plus ennuyeuses souvent nécessaires (paramètres, etc ...) sont obtenues à la volée à partir du SL. Tout le reste est injecté par le constructeur. C'est un peu moche, mais pragmatique.
maaartinus

10

Les deux sont des techniques de mise en œuvre d'IoC. Il existe également d'autres modèles qui implémentent l'inversion de contrôle:

  • Modèle d'usine
  • Localisateur de services
  • Conteneur DI (IoC)
  • Injection de dépendances (injection constructeur, injection paramètres (si non requis), injection setter d'injection interface) ...

Le localisateur de services et le conteneur DI semblent plus similaires, les deux utilisent un conteneur pour définir les dépendances, qui mappent l'abstraction à l'implémentation concrète.

La principale différence est la façon dont les dépendances sont situées, dans Service Locator, le code client demande les dépendances, dans DI Container, nous utilisons un conteneur pour créer tous les objets et il injecte des dépendances en tant que paramètres (ou propriétés) du constructeur.


7

Dans mon dernier projet, j'utilise les deux. J'utilise l'injection de dépendance pour la testabilité unitaire. J'utilise le localisateur de services pour masquer l'implémentation et être dépendant de mon conteneur IoC. et oui! Une fois que vous utilisez l'un des conteneurs IoC (Unity, Ninject, Windsor Castle), vous en dépendez. Et une fois qu'il est obsolète ou pour une raison quelconque, si vous souhaitez l'échanger, vous devrez / devrez peut-être modifier votre implémentation - au moins la racine de la composition. Mais le localisateur de services résume cette phase.

Comment ne dépendriez-vous pas de votre conteneur IoC? Soit vous devrez l'envelopper vous-même (ce qui est une mauvaise idée), soit vous utilisez Service Locator pour configurer votre conteneur IoC. Ainsi, vous direz au localisateur de services d'obtenir l'interface dont vous avez besoin et il appellera le conteneur IoC configuré pour récupérer cette interface.

Dans mon cas, j'utilise ServiceLocator qui est un composant de framework. Et utilisez Unity pour le conteneur IoC. Si à l'avenir je dois échanger mon conteneur IoC contre Ninject tout ce que je dois faire, c'est que je dois configurer mon localisateur de service pour utiliser Ninject au lieu de Unity. Migration facile.

Voici un excellent article qui explique ce scénario; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/


Le lien vers l'article johandekoning est rompu.
JakeJ

6

Je pense que les deux fonctionnent ensemble.

L'injection de dépendance signifie que vous insérez une classe / interface dépendante dans une classe consommatrice (généralement vers son constructeur). Cela dissocie les deux classes via une interface et signifie que la classe consommatrice peut fonctionner avec de nombreux types d'implémentations de "dépendance injectée".

Le rôle du localisateur de services est de regrouper votre implémentation. Vous configurez un localisateur de service via un cerclage de démarrage au début de votre programme. Le bootstrap est le processus d'association d'un type d'implémentation à un résumé / interface particulier. Qui est créé pour vous au moment de l'exécution. (basé sur votre configuration ou bootstrap). Si vous n'aviez pas implémenté l'injection de dépendance, il serait très difficile d'utiliser un localisateur de service ou un conteneur IOC.


6

Une raison d'ajouter, inspirée par une mise à jour de la documentation que nous avons écrite pour le projet MEF la semaine dernière (j'aide à construire MEF).

Une fois qu'une application est composée de milliers de composants, il peut être difficile de déterminer si un composant particulier peut être instancié correctement. Par «instancié correctement», je veux dire que dans cet exemple basé sur le Foocomposant, une instance de IBaret sera disponible, et que le composant qui le fournira:

  • avoir ses dépendances requises,
  • ne pas être impliqué dans des cycles de dépendance invalides, et
  • dans le cas du MEF, être fourni avec une seule instance.

Dans le deuxième exemple que vous avez donné, où le constructeur va dans le conteneur IoC pour récupérer ses dépendances, la seule façon de tester qu'une instance de Foopourra être instanciée correctement avec la configuration d'exécution réelle de votre application est de réellement construire il .

Cela a toutes sortes d'effets secondaires gênants au moment du test, car le code qui fonctionnera à l'exécution ne fonctionnera pas nécessairement sous un faisceau de test. Les simulations ne suffiront pas, car la configuration réelle est la chose que nous devons tester, pas une configuration de temps de test.

La racine de ce problème est la différence déjà signalée par @Jon: l'injection de dépendances via le constructeur est déclarative, tandis que la deuxième version utilise le modèle de localisateur de service impératif.

Un conteneur IoC, lorsqu'il est utilisé avec précaution, peut analyser statiquement la configuration d'exécution de votre application sans réellement créer d'instances des composants impliqués. De nombreux conteneurs populaires offrent une certaine variation de cela; Microsoft.Composition , qui est la version de MEF ciblant les applications de style Web et Metro .NET 4.5, fournit un CompositionAssertexemple dans la documentation wiki. En l'utilisant, vous pouvez écrire du code comme:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);

(Voir cet exemple ).

En vérifiant les racines de composition de de votre application au moment du test, vous pouvez potentiellement détecter certaines erreurs qui pourraient autrement passer à travers les tests plus tard dans le processus.

J'espère que c'est un ajout intéressant à cet ensemble de réponses autrement complet sur le sujet!


5

Remarque: je ne répond pas exactement à la question. Mais je pense que cela peut être utile pour les nouveaux apprenants du modèle d'injection de dépendance qui sont confondus à ce sujet avec le modèle (anti) Service Locator qui tombent par hasard sur cette page.

Je connais la différence entre le localisateur de service (il semble être considéré comme un anti-modèle maintenant) et les modèles d'injection de dépendance et je peux comprendre des exemples concrets pour chaque modèle, mais j'ai été confus par des exemples montrant un localisateur de service à l'intérieur du constructeur (supposons que nous '' faire l'injection du constructeur).

"Service Locator" est souvent utilisé à la fois comme nom d'un modèle et comme nom pour faire référence à l'objet (supposons aussi) utilisé dans ce modèle pour obtenir des objets sans utiliser le nouvel opérateur. Maintenant, ce même type d'objet peut également être utilisé à la racine de la composition de pour effectuer une injection de dépendance, et c'est là qu'intervient la confusion.

Le fait est que vous utilisez peut-être un objet de localisation de service à l'intérieur d'un constructeur DI, mais vous n'utilisez pas le "modèle de localisation de service". C'est moins déroutant si on le réfère plutôt comme un objet conteneur IoC, car vous avez peut-être deviné qu'ils font essentiellement la même chose (corrigez-moi si je me trompe).

Qu'il soit appelé localisateur de services (ou simplement localisateur) ou conteneur IoC (ou simplement conteneur) ne fait aucune différence, comme vous pouvez le deviner, car il fait probablement référence à la même abstraction (corrigez-moi si je me trompe) ). C'est juste que l'appeler un localisateur de service suggère que l'on utilise l'anti-modèle Service Locator avec le modèle d'injection de dépendance.

À mon humble avis, le nommer un «localisateur» au lieu de «emplacement» ou «localisation», peut aussi parfois faire penser que le localisateur de service dans un article se réfère au conteneur Service Locator, et non au modèle Service Locator (anti-) , en particulier lorsqu'il existe un modèle associé appelé injection de dépendance et non injecteur de dépendance.


4

Dans ce cas simplifié, il n'y a pas de différence et ils peuvent être utilisés de manière interchangeable. Cependant, les problèmes du monde réel ne sont pas aussi simples. Supposons simplement que la classe Bar elle-même avait une autre dépendance nommée D. Dans ce cas, votre localisateur de services ne pourrait pas résoudre cette dépendance et vous devrez l'instancier au sein de la classe D; car il est de la responsabilité de vos classes d'instancier leurs dépendances. Cela pourrait même empirer si la classe D elle-même avait d'autres dépendances et dans des situations réelles, cela devient généralement encore plus compliqué que cela. Dans de tels scénarios, DI est une meilleure solution que ServiceLocator.


4
Hmm je ne suis pas d'accord: le localisateur de services ex. montre clairement qu'il y a encore une dépendance ... le localisateur de services. Si la barclasse elle-même a une dépendance, elle baraura également un localisateur de service, c'est tout l'intérêt d'utiliser DI / IoC.
GFoley83

2

Quelle est la différence (le cas échéant) entre l'injection de dépendance et le localisateur de service? Les deux modèles sont bons pour implémenter le principe d'inversion de dépendance. Le modèle Service Locator est plus facile à utiliser dans une base de code existante car il rend la conception globale plus souple sans forcer les modifications de l'interface publique. Pour cette même raison, le code basé sur le modèle Service Locator est moins lisible que le code équivalent basé sur l'injection de dépendance.

Le modèle d'Injection de dépendances l'indique clairement depuis la signature des dépendances qu'une classe (ou une méthode) va avoir. Pour cette raison, le code résultant est plus propre et plus lisible.


1

Une conception simple m'a permis de mieux comprendre la différence entre Service Locator et DI Container:

  • Le localisateur de service est utilisé par le consommateur et il extrait les services par ID de certains stockages à la demande directe du consommateur

  • DI Container est situé quelque part à l'extérieur et prend des services d'un certain stockage et les pousse vers le consommateur (peu importe via le constructeur ou via la méthode)

Cependant, nous ne pouvons parler de différence entre ces derniers que dans le contexte d'une utilisation concrète par les consommateurs. Lorsque Service Locator et DI Container sont utilisés dans la racine de composition, ils sont presque similaires.


0

Le conteneur DI est un surensemble de localisateur de services. Il peut être utilisé pour localiser un service , avec une capacité supplémentaire d' assemblage (câblage) des injections de dépendance .


-2

Pour mémoire

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Sauf si vous avez vraiment besoin d'une interface (l'interface est utilisée par plusieurs classes), vous NE DEVEZ PAS L'UTILISER . Dans ce cas, IBar permet d'utiliser n'importe quelle classe de service, qui l'implémente. Cependant, généralement, cette interface sera utilisée par une seule classe.

Pourquoi est-ce une mauvaise idée d'utiliser une interface?. Parce que c'est vraiment difficile à déboguer.

Par exemple, disons que l'instance "bar" a échoué, question: quelle classe a échoué?. Quel code dois-je corriger? Une vue simple, elle mène à une Interface, et c'est ici que ma route se termine.

Au lieu de cela, si le code utilise une dépendance matérielle, il est facile de déboguer une erreur.

//Foo Needs an IBar
public class Foo
{
  private BarService bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Si "bar" échoue, je devrais vérifier et lancer la classe BarService.


1
Une classe est un plan pour construire un objet spécifique. D'un autre côté, une interface est un contractet définit simplement un comportement et non l'action. Au lieu de faire circuler l'objet réel, seule l'interface est partagée afin que le consommateur n'accède pas au reste de votre objet. De plus, pour les tests unitaires, il est utile de tester uniquement la partie qui doit être testée. Je suppose qu'avec le temps, vous comprendrez son utilité.
Gunhan
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.