【发布时间】:2018-03-27 22:11:55
【问题描述】:
最近我阅读了Mark Seemann's article 关于服务定位器反模式的文章。
作者指出了 ServiceLocator 是反模式的两个主要原因:
-
API 使用问题(我完全没问题)
当类使用服务定位器时,很难看到它的依赖关系,因为在大多数情况下,类只有一个 PARAMETERLESS 构造函数。 与 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 方法可能是更好的选择,因为它比 Service Locator 具有一些优势。
以下是使我确信在我的新项目中不使用 Service Locator 的主要区别:
- 最明显和最重要的:服务定位器隐藏了类依赖关系
- 如果您正在使用某个 IoC 容器,它可能会在启动时扫描所有构造函数以验证所有依赖关系,并在缺少映射(或错误配置)时立即向您提供反馈;如果您将 IoC 容器用作服务定位器,则这是不可能的
有关详细信息,请阅读下面给出的优秀答案。
【问题讨论】:
-
“我们不需要更新所有实例化该类的测试吗?”如果您在测试中使用构建器,则不一定正确。在这种情况下,您只需更新构建器。
-
你是对的,这取决于。例如,在大型 Android 应用程序中,到目前为止,由于低规格移动设备的性能问题,人们一直非常不愿意使用 DI。在这种情况下,您必须找到仍然编写可测试代码的替代方案,我认为在这种情况下,Service Locator 是一个足够好的替代品。 (注意:当新的 Dagger 2.0 DI 框架足够成熟时,Android 的情况可能会发生变化。)
-
请注意,自从发布了这个问题以来,Mark Seemann 的Service Locator is an Anti-Pattern post 有一个更新,描述了服务定位器如何通过破坏封装来违反 OOP,这是他迄今为止最好的论点(以及所有问题的根本原因他在所有先前的论点中使用的症状)。 2015-10-26 更新:Service Locator 的根本问题在于它violates encapsulation。
标签: design-patterns dependency-injection anti-patterns service-locator