dav*_*off 127 design-patterns dependency-injection anti-patterns service-locator
最近我读过Mark Seemann关于Service Locator反模式的文章.
作者指出ServiceLocator为反模式的两个主要原因:
API使用问题(我完全可以使用)
当类使用服务定位器时,很难看到它的依赖关系,因为在大多数情况下,类只有一个PARAMETERLESS构造函数.与ServiceLocator相比,DI方法通过构造函数的参数显式地暴露依赖关系,因此在IntelliSense中很容易看到依赖关系.
维护问题(让我感到困惑)
请考虑以下示例
我们有一个使用服务定位器方法的类'MyType':
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们要为类'MyType'添加另一个依赖项
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
// new dependency
var dep2 = Locator.Resolve<IDep2>();
dep2.DoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
这就是我的误解开始的地方.作者说:
要判断你是否引入了一个重大改变,要变得更加困难.您需要了解使用Service Locator的整个应用程序,并且编译器不会帮助您.
但是等一下,如果我们使用DI方法,我们将在构造函数中引入与另一个参数的依赖关系(在构造函数注入的情况下).问题仍然存在.如果我们忘记设置ServiceLocator,那么我们可能忘记在IoC容器中添加新的映射,并且DI方法将具有相同的运行时问题.
此外,作者还提到了单元测试的难点.但是,我们不会有DI方法的问题吗?我们不需要更新所有实例化该类的测试吗?我们将更新它们以传递一个新的模拟依赖项,以使我们的测试可编译.我没有看到更新和时间花费带来任何好处.
我不是想捍卫Service Locator方法.但这种误解让我觉得我失去了一些非常重要的东西.有人可以消除我的怀疑吗?
更新(摘要):
我的问题"服务定位器是反模式"的答案实际上取决于具体情况.我绝对不会建议你从工具列表中删除它.当您开始处理遗留代码时,它可能会变得非常方便.如果你很幸运能够处于项目的最初阶段,那么DI方法可能是更好的选择,因为它比Service Locator有一些优势.
以下是主要的不同之处,这些差异使我不相信我的新项目使用Service Locator:
有关详细信息,请阅读下面给出的优秀答案.
jga*_*fin 120
如果你将模式定义为反模式只是因为在某些情况下它不适合,那么YES就是反模式.但由于这种推理,所有模式也都是反模式.
相反,我们必须查看模式是否有有效用法,而对于Service Locator,有几个用例.但是,让我们先看看你给出的例子.
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
// new dependency
var dep2 = Locator.Resolve<IDep2>();
dep2.DoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
该类的维护噩梦是依赖项被隐藏.如果您创建并使用该类:
var myType = new MyType();
myType.MyMethod();
Run Code Online (Sandbox Code Playgroud)
如果使用服务位置隐藏它们,则您不理解它具有依赖关系.现在,如果我们改为使用依赖注入:
public class MyType
{
public MyType(IDep1 dep1, IDep2 dep2)
{
}
public void MyMethod()
{
dep1.DoSomething();
// new dependency
dep2.DoSomething();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以直接发现依赖项,并且在满足它们之前不能使用这些类.
在典型的业务应用程序中,您应该避免使用服务位置.它应该是没有其他选项时使用的模式.
没有.
例如,如果没有服务定位,控制容器的反转将无法工作.这是他们在内部解决服务的方式.
但更好的例子是ASP.NET MVC和WebApi.您认为控制器中的依赖注入可能是什么?那是对的 - 服务地点.
但是等一下,如果我们使用DI方法,我们将在构造函数中引入与另一个参数的依赖关系(在构造函数注入的情况下).问题仍然存在.
还有两个更严重的问题:
使用容器构造函数注入,您可以免费获得.
如果我们忘记设置ServiceLocator,那么我们可能忘记在IoC容器中添加新的映射,并且DI方法将具有相同的运行时问题.
确实如此.但是使用构造函数注入,您不必扫描整个类来确定缺少哪些依赖项.
一些更好的容器还会在启动时验证所有依赖项(通过扫描所有构造函数).因此,使用这些容器,您可以直接获得运行时错误,而不是稍后的时间点.
此外,作者还提到了单元测试的难点.但是,我们不会有DI方法的问题吗?
不.因为您没有依赖静态服务定位器.您是否尝试过使用静态依赖项进行并行测试?这不好玩.
jwe*_*313 37
我还想指出,如果你重构遗留代码,服务定位器模式不仅不是反模式,而且它也是一种实际需要.没有人会在数百万行代码上挥动魔杖,突然所有的代码都准备就绪了.因此,如果您想开始将DI引入现有代码库,通常情况下您会将内容更改为DI服务,并且引用这些服务的代码通常不会是DI服务.因此,这些服务将需要使用服务定位器,以获取已转换为使用DI的那些服务的实例.
因此,当重构大型遗留应用程序以开始使用DI概念时,我会说服务定位器不仅不是反模式,而且它是将DI概念逐渐应用于代码库的唯一方法.
小智 7
从测试的角度来看,Service Locator很糟糕.请参阅Misko Hevery的Google Tech Talk,并在8:45分钟开始使用代码示例http://youtu.be/RlfLCWKxHJ0.我喜欢他的比喻:如果你需要25美元,直接要钱,而不是从钱包里拿钱包.他还将服务定位器与具有您需要的针头的干草堆进行比较,并知道如何检索它.使用Service Locator的类很难重用,因为它.
维护问题(使我感到困惑)
在这方面,使用服务定位器很糟糕的原因有两个。
简单明了:带有服务定位符的类比通过其构造函数接受其依赖关系的类更难重用。
考虑一下您需要使用
LibraryA其作者决定使用ServiceLocatorA的服务和作者决定使用的服务LibraryB的情况ServiceLocatorB。除了在我们的项目中使用2个不同的服务定位器外,我们别无选择。如果我们没有好的文档,源代码或快速拨号作者,那么需要配置多少个依赖项是一个猜谜游戏。失败的这些选项,我们可能需要使用反编译器只是找出依赖关系。我们可能需要配置2个完全不同的服务定位器API,根据设计的不同,可能无法简单地包装现有的DI容器。可能根本不可能在两个库之间共享一个依赖项实例。如果服务定位符实际上不与我们需要的服务驻留在同一库中,则项目的复杂性甚至可能进一步加重-我们正在隐式将其他库引用拖到我们的项目中。现在考虑使用构造函数注入进行的两个相同的服务。添加对的引用
LibraryA。添加对的引用LibraryB。在DI配置中提供依赖项(通过Intellisense分析需要的内容)。做完了马克·西曼(Mark Seemann)有一个StackOverflow答案,以图形形式清楚地说明了这一好处,不仅适用于使用另一个库中的服务定位符,而且适用于服务中的外部默认值。