如果您将模式定义为反模式只是因为在某些情况下它不适合,那么它就是反模式。但按照这个推理,所有模式也将是反模式。
相反,我们必须查看模式是否有效,对于服务定位器来说,有几个用例。但让我们首先看一下您给出的示例。
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 方法,我们会引入一个
与构造函数中另一个参数的依赖关系(如果是
构造函数注入)。问题仍然存在。
还有两个更严重的问题:
- 通过服务定位,您还可以添加另一个依赖项:服务定位器。
- 您如何判断依赖项应具有哪个生命周期,以及应如何/何时清理它们?
通过使用容器的构造函数注入,您可以免费获得它。
如果我们可以
忘记设置 ServiceLocator,那么我们可能会忘记添加新的
我们的 IoC 容器和 DI 方法中的映射将具有相同的
运行时问题。
这是真的。但通过构造函数注入,您不必扫描整个类来找出缺少哪些依赖项。
一些更好的容器还会在启动时验证所有依赖项(通过扫描所有构造函数)。因此,使用这些容器,您会直接收到运行时错误,而不是在稍后的某个时间点。
另外,作者提到了单元测试的困难。但是,DI 方法不会有问题吗?
不会。因为您不依赖于静态服务定位器。您是否尝试过使用静态依赖项进行并行测试?这并不好玩。