web-dev-qa-db-fra.com

ServiceLocator est-il un anti-modèle?

Récemment, j'ai lu l'article de Mark Seemann à propos de l'anti-pattern Service Locator.

L'auteur souligne deux raisons principales pour lesquelles ServiceLocator est un anti-modèle:

  1. Problème d'utilisation d'API (qui me convient parfaitement)
    Lorsque la classe utilise un localisateur de service, il est très difficile de voir ses dépendances car, dans la plupart des cas, elle n’a qu’un constructeur PARAMETERLESS . Contrairement à ServiceLocator, l’approche DI expose explicitement les dépendances via les paramètres du constructeur, ce qui facilite les dépendances. vu dans IntelliSense.

  2. Problème de maintenance (ce qui me laisse perplexe)
    Considérons l’exemple suivant 

Nous avons une classe 'MyType' qui utilise une approche de localisateur de services: 

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Maintenant, nous voulons ajouter une autre dépendance à la classe 'MyType'

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Et voici où commence mon malentendu. L'auteur dit: 

Il devient beaucoup plus difficile de dire si vous introduisez un changement radical ou non. Vous devez comprendre l'ensemble de l'application dans laquelle Service Locator est utilisé et le compilateur ne va pas vous aider.

Mais attendez une seconde, si nous utilisions l'approche DI, nous introduirions une dépendance avec un autre paramètre dans le constructeur (en cas d'injection de constructeur). Et le problème sera toujours là. Si nous oublions de configurer ServiceLocator, nous risquons d’oublier d’ajouter un nouveau mappage dans notre conteneur IoC, car notre approche d’ID aurait le même problème d’exécution.

En outre, l'auteur a mentionné à propos des difficultés de test unitaire. Mais n'aurons-nous pas de problèmes avec l'approche DI? N'aurons-nous pas besoin de mettre à jour tous les tests instanciant cette classe? Nous les mettrons à jour pour passer une nouvelle dépendance simulée, juste pour rendre notre test compilable. Et je ne vois aucun avantage à cette mise à jour et à cette perte de temps.

Je n'essaie pas de défendre l'approche Service Locator. Mais ce malentendu me fait penser que je perds quelque chose de très important. Quelqu'un pourrait-il dissiper mes doutes?

UPDATE (RÉSUMÉ):

La réponse à ma question "Est-ce que Service Locator est-il un anti-modèle" dépend vraiment des circonstances? Et je ne recommanderais certainement pas de le rayer de votre liste d'outils. Cela peut devenir très pratique lorsque vous commencez à utiliser le code hérité. Si vous êtes assez chanceux pour être au tout début de votre projet, l'approche ID pourrait être un meilleur choix car elle présente certains avantages par rapport à Service Locator.

Et voici les principales différences qui m'ont convaincu de ne pas utiliser Service Locator pour mes nouveaux projets:

  • Le plus évident et le plus important: Service Locator masque les dépendances de classe.
  • Si vous utilisez un conteneur IoC, il analysera probablement tous les constructeurs au démarrage pour valider toutes les dépendances et vous donner un retour immédiat sur les mappages manquants (ou une configuration incorrecte). cela n'est pas possible si vous utilisez votre conteneur IoC en tant que service de localisation

Pour plus de détails, lisez les excellentes réponses données ci-dessous.

120
davidoff

Si vous définissez des motifs comme anti-motifs simplement parce qu'il y a des situations où cela ne correspond pas, alors OUI c'est un anti-motif. Mais avec ce raisonnement, tous les modèles seraient également anti-modèles.

Au lieu de cela, nous devons rechercher s'il existe des utilisations valides des modèles et pour Service Locator, il existe plusieurs cas d'utilisation. Mais commençons par regarder les exemples que vous avez donnés.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Le cauchemar de maintenance avec cette classe est que les dépendances sont masquées. Si vous créez et utilisez cette classe:

var myType = new MyType();
myType.MyMethod();

Vous ne comprenez pas qu'il existe des dépendances si elles sont masquées à l'aide de l'emplacement du service. Maintenant, si nous utilisons plutôt l'injection de dépendance:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Vous pouvez directement repérer les dépendances et ne pouvez pas utiliser les classes avant de les satisfaire.

Dans une application métier typique, vous devez éviter l'utilisation de l'emplacement de service pour cette raison même. Ce devrait être le modèle à utiliser quand il n'y a pas d'autres options.

Le motif est-il un anti-motif?

Non. 

Par exemple, l'inversion des conteneurs de contrôle ne fonctionnerait pas sans l'emplacement du service. C'est comment ils résolvent les services en interne.

ASP.NET MVC et WebApi constituent un exemple bien meilleur. Selon vous, qu'est-ce qui rend l'injection de dépendance possible dans les contrôleurs? C'est vrai - lieu de service.

Vos questions

Mais attendez une seconde, si nous utilisions l'approche DI, nous introduirions un dépendance avec un autre paramètre dans le constructeur (dans le cas d'une injection de constructeur .__). Et le problème sera toujours là. 

Il y a deux problèmes plus graves:

  1. Avec l'emplacement de service, vous ajoutez également une autre dépendance: le localisateur de service. 
  2. Comment dites-vous quelle durée de vie les dépendances devraient avoir et comment/quand elles devraient être nettoyées?

Avec l'injection de constructeur utilisant un conteneur, vous l'obtenez gratuitement.

Si nous pouvons oubliez pas de configurer ServiceLocator, nous risquons d’oublier d’ajouter un nouveau la cartographie dans notre conteneur IoC et notre approche DI auraient la même chose problème d'exécution.

C'est vrai. Mais avec l'injection de constructeur, il n'est pas nécessaire d'analyser toute la classe pour déterminer les dépendances manquantes. 

Et certains meilleurs conteneurs valident également toutes les dépendances au démarrage (en analysant tous les constructeurs). Donc, avec ces conteneurs, vous obtenez l'erreur d'exécution directement, et pas à un moment ultérieur.

En outre, l'auteur a mentionné à propos des difficultés de test unitaire. Mais n'aurons-nous pas de problèmes avec l'approche DI? 

Non, car vous n’avez pas de dépendance à un localisateur de service statique. Avez-vous essayé de faire fonctionner des tests parallèles avec des dépendances statiques? Ce n'est pas amusant.

110
jgauffin

J'aimerais également souligner que SI vous refactorisez du code hérité, le modèle Service Locator n'est pas non plus un anti-modèle, mais une nécessité pratique. Personne ne va jamais agiter une baguette magique sur des millions de lignes de code et tout ce code sera soudainement prêt à l'emploi. Donc, si vous souhaitez commencer à introduire DI dans une base de code existante, il est fréquent que vous modifiiez les choses pour devenir des services DI, et le code qui référence ces services ne sera souvent PAS des services DI. Par conséquent, CES services devront utiliser le localisateur de services pour obtenir des instances de ces services convertis pour utiliser DI.

Ainsi, lors de la refactorisation d'applications volumineuses héritées pour commencer à utiliser les concepts DI, je dirais que non seulement Service Locator N'EST PAS un anti-modèle, mais que c'est le seul moyen d'appliquer progressivement les concepts DI à la base de code.

34
jwells131313

Du point de vue des tests, Service Locator est mauvais. Voir Google Tech Talk de Misko Hevery. Belle explication avec des exemples de code http://youtu.be/RlfLCWKxHJ0 à partir de la minute 8:45. J'ai bien aimé son analogie: si vous avez besoin de 25 dollars, demandez directement de l'argent plutôt que de donner votre portefeuille à partir duquel l'argent sera pris. Il compare également Service Locator avec une botte de foin dotée de l'aiguille dont vous avez besoin et qui sait comment la récupérer. Les classes utilisant Service Locator sont difficiles à réutiliser à cause de cela.

7
user3511397

Problème de maintenance (ce qui me laisse perplexe)

Il y a 2 raisons différentes pour lesquelles utiliser Service Locator est mauvais à cet égard.

  1. Dans votre exemple, vous codez en dur une référence statique au localisateur de services dans votre classe. Ceci couple étroitement votre classe directement au localisateur de services, ce qui signifie à son tour il ne fonctionnera pas sans le localisateur de services. De plus, vos tests unitaires (et toute autre personne utilisant la classe) dépendent également implicitement du localisateur de service. Une chose qui semble passer inaperçue ici est que lorsque vous utilisez l'injection de constructeur, vous n'avez pas besoin d'un conteneur DI pour les tests unitaires, ce qui simplifie considérablement vos tests unitaires (et la capacité des développeurs à les comprendre). C’est l’avantage concret obtenu par l’utilisation de l’injection par constructeur.
  2. En ce qui concerne la raison pour laquelle le constructeur Intellisense est important, les gens ici semblent avoir complètement manqué le propos. Une classe est écrite une fois, mais il peut être utilisé dans plusieurs applications (c'est-à-dire plusieurs configurations DI). Au fil du temps, il est avantageux de consulter la définition du constructeur pour comprendre les dépendances d'une classe, plutôt que de consulter la documentation (espérons-le à jour) ou, à défaut, de revenir au code source d'origine (ce qui pourrait ne pas être le cas). être pratique) pour déterminer les dépendances d’une classe. La classe avec le localisateur de services est généralement plus facile à écrire, mais vous payez plus que le coût de cette commodité pour la maintenance continue du projet.

En clair: une classe contenant un localisateur de services est plus difficile à réutiliser qu'une autre qui accepte ses dépendances via son constructeur.

Prenons le cas où vous devez utiliser un service de LibraryA que son auteur a décidé d'utiliser ServiceLocatorA et un service de LibraryB dont l'auteur a décidé d'utiliser ServiceLocatorB. Nous n'avons pas d'autre choix que d'utiliser 2 localisateurs de services différents dans notre projet. Le nombre de dépendances à configurer est un jeu de devinettes si nous n’avons pas une bonne documentation, le code source ou l’auteur en composition abrégée. À défaut de ces options, nous aurons peut-être besoin d'un décompilateur just pour déterminer les dépendances. Nous aurons peut-être besoin de configurer 2 API de localisateur de services totalement différentes et, selon la conception, il ne sera peut-être pas possible d'envelopper simplement votre conteneur DI existant. Il peut ne pas être possible du tout de partager une instance d'une dépendance entre les deux bibliothèques. La complexité du projet pourrait même être aggravée si les localisateurs de services ne résidaient pas dans les mêmes bibliothèques que les services dont nous avions besoin - nous faisons implicitement glisser des références de bibliothèque supplémentaires dans notre projet.

Considérons maintenant les deux mêmes services réalisés avec l’injection de constructeur. Ajoutez une référence à LibraryA. Ajoutez une référence à LibraryB. Indiquez les dépendances dans votre configuration DI (en analysant les besoins via Intellisense). Terminé.

Mark Seemann a une réponse StackOverflow qui illustre clairement cet avantage sous forme graphique , qui s'applique non seulement lors de l'utilisation d'un localisateur de services d'une autre bibliothèque, mais également lors de l'utilisation de valeurs par défaut étrangères dans les services.

3
NightOwl888

Mes connaissances ne sont pas suffisantes pour en juger, mais en général, je pense que si quelque chose a une utilité dans une situation donnée, cela ne signifie pas nécessairement que cela ne peut pas être un anti-modèle. En particulier, lorsque vous traitez avec des bibliothèques tierces, vous n’avez pas le contrôle total sur tous les aspects et vous risquez de vous retrouver avec la solution qui n’est pas la meilleure. 

Voici un paragraphe de Code adaptatif via C # :

"Malheureusement, le localisateur de services est parfois un anti-modèle inévitable. Dans certains types d'applications, en particulier Windows Workflow Foundation, l'infrastructure ne se prête pas à l'injection de constructeur. Dans ce cas, la seule alternative est d'utiliser un localisateur de services. mieux que de ne pas injecter de dépendances du tout. Pour tout mon vitriol contre le (anti) motif, il est infiniment mieux que de construire manuellement des dépendances. Après tout, il permet toujours ces points d’extension très importants fournis par des interfaces permettant aux décorateurs, aux adaptateurs, et avantages similaires. "

- Hall, Gary McLean. Code adaptatif via C #: codage agile avec des modèles de conception et les principes SOLID (Référence du développeur) (p. 309). Pearson Education.

1
Siavash Mortazavi

Oui, le localisateur de services est un anti-modèle il viole l’encapsulation et solide .

L’auteur déclare que "le compilateur ne vous aidera pas" - et il est vrai que ... lorsque vous concevez une classe, vous devez choisir avec soin son interface - entre autres objectifs pour la rendre aussi indépendante que ... Ca a du sens.

En demandant au client d’accepter la référence à un service (à une dépendance) via une interface explicite, vous

  • obtenir implicitement la vérification, de sorte que le compilateur "aide".
  • Vous supprimez également le besoin pour le client de savoir quelque chose à propos du "localisateur" ou de mécanismes similaires, de sorte que le client soit en réalité plus indépendant.

Vous avez raison de dire que DI a ses problèmes/inconvénients, mais les avantages mentionnés les surpassent de loin ... IMO ..__ Vous avez raison, avec DI, une dépendance est introduite dans l'interface (constructeur) - mais cela Espérons que c’est la dépendance dont vous avez besoin et que vous voulez rendre visible et contrôlable.

0
Zrin