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:
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.
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:
Pour plus de détails, lisez les excellentes réponses données ci-dessous.
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.
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.
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:
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.
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.
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.
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.
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'utiliserServiceLocatorA
et un service deLibraryB
dont l'auteur a décidé d'utiliserServiceLocatorB
. 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.
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.
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
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.