最近,我读过马克·西曼的文章有关服务定位器反模式。
作者指出了两个主要的原因是的ServiceLocator反模式:
API的使用问题 (这我完全罚款)
当类使用了服务定位器这是很难看到它的依赖关系,在大多数情况下,类只有一个参数的构造函数。 与服务定位相比之下,DI方法清楚地暴露通过构造函数的参数依赖关系,以便依赖在智能感知容易看到。
维护问题 (这让我为难)
考虑以下expample
我们有一类“的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的方法,我们会在构造函数中引入的依赖与其他参数(在构造函数注入的情况下)。 而问题将依然存在。 如果我们可能会忘记设置服务定位,那么我们可能会忘记在我们的IoC容器和DI方法添加一个新的映射也有同样的运行时间的问题。
此外,笔者提及的单元测试的困难。 但是,会不会我们与DI方法的问题? 会不会我们需要更新其被实例化类中的所有测试? 我们将更新他们通过一项新的嘲笑依赖只是为了让我们的测试编译。 而且我没有看到从更新和时间花费任何好处。
我并不想保卫服务定位的方法。 但是,这种误解让我觉得我失去了很重要的东西。 可能有人打消了我的疑虑?
UPDATE(概要):
我的问题答案是“服务定位器反模式”实际上取决于具体情况。 而且我绝对不会建议从您的工具列表,把它划掉。 当你开始处理遗留代码它可能会变得非常方便。 如果你足够幸运是在项目的一开始就那么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();
}
}
您可以直接发现的依赖关系,并满足他们之前无法使用的类。
在商业应用的一个典型的线,你应该避免正是由于这个原因,使用服务的位置。 它应该是在没有其他选择的是使用模式。
是模式的反模式?
没有。
例如,控制容器的反转不会没有服务点工作。 这是他们如何解决内部的服务。
但是,一个更好的例子是ASP.NET MVC和的WebAPI。 你认为使依赖注入可能在控制器? 这是正确的 - 服务的位置。
你的问题
但还有一个问题,如果我们使用DI的方法,我们会在构造函数中引入的依赖与其他参数(在构造函数注入的情况下)。 而问题将依然存在。
有两个更严重的问题:
- 随着服务的位置,你还添加其它的依赖:服务定位器。
- 你怎么知道的依赖关系应该具有的寿命,以及如何/当他们应该得到清理?
对于使用的容器构造器注入你会得到免费的。
如果我们可能会忘记设置服务定位,那么我们可能会忘记在我们的IoC容器和DI方法添加一个新的映射也有同样的运行时间的问题。
确实如此。 但随着构造函数注入你不必扫描整个班级找出哪些依赖失踪。
而一些较好的容器也验证所有的依赖在启动时(通过扫描所有构造函数)。 因此,与这些容器直接得到运行时错误,而不是在以后的某个时间点。
此外,笔者提及的单元测试的困难。 但是,会不会我们与DI方法的问题?
否。你不必依赖于静态的服务定位器。 你有没有试图让与静态依赖工作并行测试? 这不好玩。
我还想指出的是,如果你是重构遗留代码Service Locator模式不但不反模式,但它也是一种实际需要。 没有人是以往任何时候都在数百万行代码的挥动魔杖,突然所有的代码将是DI准备。 所以,如果你想开始引进DI到现有的代码库,它往往是,你会改变的东西成为DI缓缓服务的情况,并引用这些服务通常不会是DI服务代码。 因此,这些服务将需要使用服务定位器,以获得已转换使用DI这些服务的实例。
因此,重构大型遗留应用程序时,开始使用DI概念我要说的是,不仅是服务定位器不是一个反模式,但它是DI的概念逐渐应用到代码库的唯一途径。
但从检查点,服务定位器是坏的。 请参阅使用代码示例MISKO Hevery的谷歌技术讲座很好的解释http://youtu.be/RlfLCWKxHJ0开始分钟8:45。 我喜欢他的比喻:如果你需要$ 25直接要钱,而不是给从那里的钱将被带到你的钱包。 他还服务定位器与具有你所需要的针头,并且知道如何找回它草堆进行比较。 使用服务定位器类是困难的,因为它的重用。
维护问题 (这让我为难)
有2个不同的原因,使用服务定位器是坏在这方面。
- 在你的榜样,你是硬编码的静态引用服务定位到类。 这种紧耦合的直接类的服务定位器,这反过来意味着它不会在未得到服务定位器功能 。 此外,单元测试(和其他人谁使用类)也含蓄地依赖于服务定位器。 这似乎已被忽视,这里的一件事是使用构造器注入时,你并不需要一个DI容器时,单元测试 ,从而简化了单元测试(和开发者了解他们的能力)相当。 这是你使用构造器注入得到实现单元测试的好处。
- 至于为什么构造智能感知是很重要的,这里的人似乎已经完全忽略了一点。 一类是写一次,但也可以在多种应用中使用(即,几个DI配置)。 随着时间的推移,它支付的股息,如果你可以看看构造函数定义来理解类的依赖关系,而不是看(希望跟上时代的)文档,或做不到这一点,要回原来的源代码(这可能不是很方便),以确定哪些类的依赖关系。 与服务定位器类是一般不是支付这种便利的费用在项目的日常维护更容易写 ,但你更多。
平原和简单:用服务定位器一类是比较困难比一个通过它的构造函数接受它的依赖重用 。
考虑你需要使用从服务的情况下LibraryA
是它的作者决定将使用ServiceLocatorA
和服务LibraryB
其作者决定将使用ServiceLocatorB
。 我们必须比我们的项目中使用2个不同的服务定位器别无选择。 有多少依赖需要进行配置是一个猜谜游戏,如果我们没有好的文档,源代码,或者在快速拨号作者。 失败的这些选项,我们可能需要使用反编译器只是弄清楚的依赖是什么。 我们可能需要配置2个完全不同的服务定位器API,以及根据设计,它可能无法简单地包装现有的DI容器。 它可能无法在所有共享两个库之间的依赖关系的一个实例。 该项目的复杂性,甚至可以进一步加剧,如果服务定位器不发生实际驻留在同一库,因为我们需要的服务 - 我们都隐含拖动额外的库引用到我们的项目。
现在考虑构造器注入由两个相同的服务。 加入一个参考LibraryA
。 加入一个参考LibraryB
。 (通过分析通过智能感知所需要的),请提供DI配置的依赖关系。 完成。
马克西曼有StackOverflow的答案是清楚地说明了以图表的形式这样做的好处 ,它不仅使用从另一个图书馆服务定位器时,也使用服务的外国违约时适用。
我的知识不够好判断这个,但总的来说,我觉得如果有东西在特定的情况使用,这并不一定意味着它不能是一个反模式。 特别是,当你正在处理第三方库,你不必对所有方面的完全控制,你可能最终使用的不是最好的解决方案。
下面是从自适应编码通过C#中的一段话:
“不幸的是,服务定位器有时是不可避免的反模式,在某些应用程序类型 - 尤其是Windows工作流基金会 - 基础设施本身不适合于构造器注入。在这种情况下,唯一的选择是使用一个服务定位器。这是比未注射的所有依赖性更好。对于我反对(防)模式的所有矾,它远比人工构建的依赖性比较好。毕竟,它仍然能够通过允许装饰,电源适配器接口,所提供的所有重要的扩展点,和类似的利益。”
- 霍尔,加里·麦克莱恩。 通过C#自适应代码:敏捷编码设计模式和SOLID原则(开发人员参考)(第309页)。 培生教育。
作者原因“编译器不会帮你” - 这是真实的。 当您屈尊一个类时,你将要谨慎选择它的界面 - 其他目标中,使其作为独立的...它是有意义的。
通过让客户接受通过显式接口的参考服务(到依赖),你
- 含蓄得到检查,所以编译器“帮助”。
- 你也消除了对客户端知道一些关于“定位器”或类似机制的需求,因此客户实际上是更加独立。
你说得对,DI有问题/缺点,但提到利大于弊他们迄今为止... IMO。 你说得对,与DI有在接口(构造)中引入的依赖 - 但这个希望很依赖,你需要和你想使可见和可检查的。