问题描述
最近我已经阅读了 Mark Seemann的文章关于服务定位的文章反图案.
作者指出了ServiceLocator是反模式的两个主要原因:
-
API用法问题(我完全可以)
当类采用服务定位器时,很难看到其依赖关系,因为在大多数情况下,类只有一个无参数构造函数. 与ServiceLocator相反,DI方法通过构造函数的参数明确公开依赖关系,因此在Intellisense中很容易看到依赖项. -
维护问题(使我感到困惑)
考虑以下启动
我们有一个类'mytype'采用服务定位器方法:
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); } }
现在,我们想为" mytype"类添加另一个依赖项
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); // new dependency var dep2 = Locator.Resolve<IDep2>(); dep2.DoSomething(); } }
这是我的误解开始的地方.作者说:
很难说出您是否正在引入破坏变化.您需要了解使用服务定位器的整个应用程序,并且编译器不会为您提供帮助.
但是请等待一秒钟,如果我们使用DI方法,我们将在构造函数中引入另一个参数(如果是构造函数注入).问题仍然存在.如果我们可能忘记了设置ServiceLocator,那么我们可能会忘记在IOC容器中添加新的映射,并且DI方法将具有相同的运行时间问题.
另外,作者也提到了单位测试困难.但是,我们在DI方法上不会有问题吗?我们不需要更新实例化该课程的所有测试吗?我们将更新它们以通过新的模拟依赖性,以使我们的测试可编译.而且我看不到该更新和时间支出的任何好处.
我不是要捍卫服务定位者的方法.但是,这种误解使我认为我失去了非常重要的事情.有人可以消除我的疑问吗?
更新(摘要):
我的问题的答案"服务定位器是反模式"的确取决于情况.而且我绝对不建议您从您的工具列表中划出它.当您开始处理旧版代码时,它可能会变得非常方便.如果您很幸运能够进入项目的一开始,那么DI方法可能是一个更好的选择,因为它比服务定位器具有一些优势.
这是主要区别,使我说服我不将服务定位器用于我的新项目:
- 最明显和重要的:服务定位器隐藏班级依赖
- 如果您使用某些IOC容器,则可能会在启动时扫描所有构造函数以验证所有依赖关系,并为您提供有关缺失映射(或错误配置)的立即反馈;如果您将IOC容器用作服务定位器 ,这是不可能的
有关详细信息,请阅读以下提供的出色答案.
推荐答案
如果您仅仅因为某些情况不合适而将模式定义为抗模式,那么它是一种反模式.但是,通过推理所有模式也将是反模式.
相反,我们必须查看模式是否有效用法,对于服务定位器,有几种用例.但是,让我们从查看您给出的示例开始.
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); // new dependency var dep2 = Locator.Resolve<IDep2>(); dep2.DoSomething(); } }
与该类别的维护噩梦是隐藏了依赖关系.如果您创建并使用该类:
var myType = new MyType(); myType.MyMethod();
您不明白,如果使用服务位置隐藏,则它具有依赖关系.现在,如果我们相反使用依赖注入:
public class MyType { public MyType(IDep1 dep1, IDep2 dep2) { } public void MyMethod() { dep1.DoSomething(); // new dependency dep2.DoSomething(); } }
您可以直接发现依赖项,并且不能在满足这些类之前使用这些依赖项.
在典型的业务应用程序中,您应该避免出于此原因使用服务位置.当没有其他选项时,应该是使用的模式.
模式是抗模式吗?
no.
例如,如果没有服务位置,控制容器的反转将无法工作.这就是他们内部解决服务的方式.
但是,一个更好的例子是ASP.NET MVC和WebAPI.您认为如何使控制器的依赖注入成为可能?是的 - 服务位置.
您的问题
但是请等一下,如果我们使用DI方法,我们会引入一个 依赖性与构造函数中的另一个参数(如果是 构造仪注入).问题仍然存在.
还有两个严重的问题:
- 在服务位置,您还添加了另一个依赖性:服务定位器.
- 您如何判断依赖性应该拥有哪个寿命,以及如何/何时清理?
使用构造函数使用容器,您可以免费获得.
如果我们可以 忘记设置服务剂,然后我们可能会忘记添加一个新的 在我们的IOC容器和DI方法中映射将具有相同的 运行时问题.
是的.但是使用构造函数注入,您不必扫描整个课程即可确定缺少哪些依赖性.
和一些更好的容器还可以验证启动时的所有依赖项(通过扫描所有构造函数).因此,使用这些容器,您可以直接获得运行时错误,而不是在以后的时间点上.
另外,作者也提到了单位测试困难.但是,我们不会在DI方法上遇到问题吗?
否.因为您对静态服务定位器没有依赖性.您是否试图获得与静态依赖关系一起使用的并行测试?这没什么好玩的.
其他推荐答案
我还想指出,如果您是在重构遗留代码,那么服务定位器模式不仅不是反模式,而且也是实际的必要性.没有人会挥舞着数百万行代码的魔杖,突然间,所有代码都将准备就绪.因此,如果您想开始将DI引入现有的代码库,通常您会更改慢慢成为DI服务的事物,并且引用这些服务通常不是DI服务的代码.因此,这些服务将需要使用服务定位器,以获取已转换为使用DI的服务的实例.
因此,当重构大型遗产应用程序开始使用DI概念时,我会说,不仅服务定位器不是反模式,而且是逐渐将DI概念应用于代码库的唯一方法.
其他推荐答案
从测试的角度来看,服务定位器不好.请参阅Misko Hevery的Google Tech Talk Talk,与代码示例的很好解释 http://youtu.be/rllflcwkxhj0 从第8:45分钟开始.我喜欢他的类比:如果您需要25美元,请直接索要钱,而不是从拿钱的地方捐出钱包.他还将服务定位器与带有您需要的针头的干草堆进行了比较,并且知道如何检索它.使用服务定位器的类很难重复使用.
问题描述
Recently I've read Mark Seemann's article about Service Locator anti-pattern.
Author points out two main reasons why ServiceLocator is an anti-pattern:
API usage issue (which I'm perfectly fine with)
When class employs a Service locator it is very hard to see its dependencies as, in most cases, class has only one PARAMETERLESS constructor. In contrast with ServiceLocator, DI approach explicitly exposes dependencies via constructor's parameters so dependencies are easily seen in IntelliSense.Maintenance issue (which puzzles me)
Consider the following expample
We have a class 'MyType' which employs a Service locator approach:
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); } }
Now we want to add another dependency to class 'MyType'
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); // new dependency var dep2 = Locator.Resolve<IDep2>(); dep2.DoSomething(); } }
And here is where my misunderstanding starts. The author says:
It becomes a lot harder to tell whether you are introducing a breaking change or not. You need to understand the entire application in which the Service Locator is being used, and the compiler is not going to help you.
But wait a second, if we were using DI approach, we would introduce a dependency with another parameter in constructor (in case of constructor injection). And the problem will be still there. If we may forget to setup ServiceLocator, then we may forget to add a new mapping in our IoC container and DI approach would have the same run-time problem.
Also, author mentioned about unit test difficulties. But, won't we have issues with DI approach? Won't we need to update all tests which were instantiating that class? We will update them to pass a new mocked dependency just to make our test compilable. And I don't see any benefits from that update and time spending.
I'm not trying to defend Service Locator approach. But this misunderstanding makes me think that I'm losing something very important. Could somebody dispel my doubts?
UPDATE (SUMMARY):
The answer for my question "Is Service Locator an anti-pattern" really depends upon the circumstances. And I definitely wouldn't suggest to cross it out from your tool list. It might become very handy when you start dealing with legacy code. If you're lucky enough to be at the very beginning of your project then the DI approach might be a better choice as it has some advantages over Service Locator.
And here are main differences which convinced me to not use Service Locator for my new projects:
- Most obvious and important: Service Locator hides class dependencies
- If you are utilizing some IoC container it will likely scan all constructor at startup to validate all dependencies and give you immediate feedback on missing mappings (or wrong configuration); this is not possible if you're using your IoC container as Service Locator
For details read excellent answers which are given below.
推荐答案
If you define patterns as anti-patterns just because there are some situations where it does not fit, then YES it's an anti pattern. But with that reasoning all patterns would also be anti patterns.
Instead we have to look if there are valid usages of the patterns, and for Service Locator there are several use cases. But let's start by looking at the examples that you have given.
public class MyType { public void MyMethod() { var dep1 = Locator.Resolve<IDep1>(); dep1.DoSomething(); // new dependency var dep2 = Locator.Resolve<IDep2>(); dep2.DoSomething(); } }
The maintenance nightmare with that class is that the dependencies are hidden. If you create and use that class:
var myType = new MyType(); myType.MyMethod();
You do not understand that it has dependencies if they are hidden using service location. Now, if we instead use dependency injection:
public class MyType { public MyType(IDep1 dep1, IDep2 dep2) { } public void MyMethod() { dep1.DoSomething(); // new dependency dep2.DoSomething(); } }
You can directly spot the dependencies and cannot use the classes before satisfying them.
In a typical line of business application you should avoid the use of service location for that very reason. It should be the pattern to use when there are no other options.
Is the pattern an anti-pattern?
No.
For instance, inversion of control containers would not work without service location. It's how they resolve the services internally.
But a much better example is ASP.NET MVC and WebApi. What do you think makes the dependency injection possible in the controllers? That's right -- service location.
Your questions
But wait a second, if we were using DI approach, we would introduce a dependency with another parameter in constructor (in case of constructor injection). And the problem will be still there.
There are two more serious problems:
- With service location you are also adding another dependency: The service locator.
- How do you tell which lifetime the dependencies should have, and how/when they should get cleaned up?
With constructor injection using a container you get that for free.
If we may forget to setup ServiceLocator, then we may forget to add a new mapping in our IoC container and DI approach would have the same run-time problem.
That's true. But with constructor injection you do not have to scan the entire class to figure out which dependencies are missing.
And some better containers also validate all dependencies at startup (by scanning all constructors). So with those containers you get the runtime error directly, and not at some later temporal point.
Also, author mentioned about unit test difficulties. But, won't we have issues with DI approach?
No. As you do not have a dependency to a static service locator. Have you tried to get parallel tests working with static dependencies? It's not fun.
其他推荐答案
I would also like to point out that IF you are refactoring legacy code that the Service Locator pattern is not only not an anti-pattern, but it is also a practical necessity. No-one is ever going to wave a magic wand over millions of lines of code and suddenly all that code is going to be DI ready. So if you want to start introducing DI to an existing code base it is often the case that you will change things to become DI services slowly, and the code that references these services will often NOT be DI services. Hence THOSE services will need to use the Service Locator in order to get instances of those services that HAVE been converted to use DI.
So when refactoring large legacy applications to start to use DI concepts I would say that not only is Service Locator NOT an anti-pattern, but that it is the only way to gradually apply DI concepts to the code base.
其他推荐答案
From the testing point of view, Service Locator is bad. See Misko Hevery's Google Tech Talk nice explanation with code examples http://youtu.be/RlfLCWKxHJ0 starting at minute 8:45. I liked his analogy: if you need $25, ask directly for money rather than giving your wallet from where money will be taken. He also compares Service Locator with a haystack that has the needle you need and knows how to retrieve it. Classes using Service Locator are hard to reuse because of it.