这两种模式看起来都像是控制反转原理的实现.也就是说,一个对象不应该知道如何构造它的依赖关系.
依赖注入(DI)似乎使用构造函数或setter来"注入"它的依赖项.
使用构造函数注入的示例:
//Foo Needs an IBar public class Foo { private IBar bar; public Foo(IBar bar) { this.bar = bar; } //... }
服务定位器似乎使用了一个"容器",它连接了它的依赖关系并给它foo吧.
使用服务定位器的示例:
//Foo Needs an IBar public class Foo { private IBar bar; public Foo() { this.bar = Container.Get(); } //... }
因为我们的依赖项只是对象本身,所以这些依赖项具有依赖项,它们具有更多依赖项,依此类推.因此,控制容器的反转(或DI容器)诞生了.示例:Castle Windsor,Ninject,Structure Map,Spring等)
但是,IOC/DI容器看起来完全相同像一个服务定位器.将它称为DI容器是一个坏名字?IOC/DI容器只是另一种服务定位器吗?当我们有很多依赖关系时,我们使用DI容器这一事实的细微差别是什么?
差异可能看起来很小,但即使使用ServiceLocator,该类仍然负责创建其依赖项.它只是使用服务定位器来完成它.使用DI,该类被赋予其依赖性.它既不知道,也不关心它们来自何处.这样做的一个重要结果是DI示例更容易进行单元测试 - 因为您可以将其依赖对象的模拟实现传递给它.如果需要,您可以将两者结合起来 - 并注入服务定位器(或工厂).
使用服务定位器时,每个类都将依赖于服务定位器.依赖注入不是这种情况.依赖注入器通常在启动时仅调用一次,以将依赖项注入某个主类.这个主类所依赖的类将递归地注入它们的依赖项,直到你有一个完整的对象图.
一个很好的比较:http://martinfowler.com/articles/injection.html
如果您的依赖注入器看起来像服务定位器,类直接调用注入器,它可能不是依赖注入器,而是服务定位器.
服务定位器隐藏依赖关系 - 当从对象获取连接时,您无法通过查看对象是否访问数据库(例如).使用依赖项注入(至少构造函数注入),依赖项是显式的.
此外,服务定位器打破了封装,因为它们提供了对其他对象的依赖关系的全局访问点.使用服务定位器,与任何单例一样:
为客户端对象的接口指定前置和后置条件变得很困难,因为其实现的工作可以从外部进行干预.
使用依赖项注入,一旦指定了对象的依赖项,它们就会受到对象本身的控制.
Martin Fowler说:
使用服务定位器,应用程序类通过发送给定位器的消息明确地请求它.使用注入没有明确的请求,服务出现在应用程序类中 - 因此控制反转.
简而言之:服务定位器和依赖注入只是依赖性倒置原则的实现.
重要的原则是"取决于抽象,而不是取决于具体结果".这将使您的软件设计"松散耦合","可扩展","灵活".
您可以使用最适合您需求的那个.对于具有庞大代码库的大型应用程序,您最好使用服务定位器,因为依赖注入需要对代码库进行更多更改.
您可以查看以下帖子:依赖性反转:服务定位器或依赖注入
经典之作:控制容器的反转和依赖注入模式由Martin Fowler提供
由Ralph E. Johnson和Brian Foote 设计可重复使用的课程
然而,让我眼前一亮的是:ASP.NET MVC:Resolve或Inject?那就是Dino Esposito的问题......
使用构造函数DI的类指示消耗代码以满足依赖性.如果类在内部使用SL来检索此类依赖项,则使用代码不会识别依赖项.从表面上看,这似乎更好,但知道任何明确的依赖关系实际上是有帮助的.从架构的角度来看,它更好.在进行测试时,您必须知道某个类是否需要某些依赖项,并配置SL以提供这些依赖项的适当虚假版本.使用DI,只需传入假货.没有太大的区别,但它确实存在.
但是,DI和SL可以协同工作.具有公共依赖关系的中心位置(例如设置,记录器等)是有用的.给定使用此类deps的类,您可以创建一个接收deps的"真实"构造函数,以及一个从SL检索并转发到"real"构造函数的默认(无参数)构造函数.
编辑:当然,当你使用SL时,你会引入一些与该组件的耦合.这是具有讽刺意味的,因为这种功能的想法是鼓励抽象并减少耦合.问题可以平衡,这取决于您需要使用SL的地方数量.如果按照上面的建议完成,只需在默认的类构造函数中完成.
它们都是IoC的实现技术.还有其他模式实现控制反转:
工厂模式
服务定位器
依赖注入(构造函数注入,参数注入(如果不需要),接口注入的setter注入)......
服务定位器和DI看起来更相似,它们都使用容器来定义依赖关系,它将抽象映射到具体实现.
主要区别在于依赖关系是如何定位的,在Service Location客户端代码中请求依赖关系,在DI中我们使用容器来创建所有对象,并且它将依赖关系注入构造函数参数(或属性).
在我的上一个项目中,我使用了两者 我使用依赖注入来实现单元可测试性.我使用服务定位器来隐藏实现并依赖于我的IoC容器.是的!一旦你使用了一个IoC容器(Unity,Ninject,Windsor Castle),你就依赖它了.一旦它过时或出于某种原因你想要交换它,你将/可能需要改变你的实现 - 至少是组合根.但是服务定位器抽象了那个阶段.
你怎么不依赖你的IoC容器?要么你需要自己包装(这是一个坏主意),要么使用Service Locator配置你的IoC容器.因此,您将告诉服务定位器获取所需的接口,并调用配置为检索该接口的IoC容器.
在我的例子中,我使用ServiceLocator作为框架组件.并使用Unity for IoC容器.如果将来我需要将我的IoC容器交换到Ninject我需要做的是我需要配置我的服务定位器以使用Ninject而不是Unity.轻松迁移.
这是一篇很棒的文章解释了这个场景; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/
我认为这两个一起工作.
依赖注入意味着您将一些依赖的类/接口推送到消费类(通常是它的构造函数).这通过接口将两个类分离,这意味着消费类可以使用许多类型的"注入依赖"实现.
服务定位器的作用是将您的实现整合在一起.您可以在程序开始时通过一些引导捆绑设置服务定位器.Bootstrapping是将一种实现与特定抽象/接口相关联的过程.这是在运行时为您创建的.(基于你的配置或引导程序).如果您尚未实现依赖注入,则使用服务定位器或IOC容器将非常困难.
添加的一个原因,受到我们上周为MEF项目编写的文档更新的启发(我帮助构建MEF).
一旦应用程序由可能数千个组件组成,就很难确定是否可以正确地实例化任何特定组件.通过"正确实例化",我的意思是在这个基于Foo
组件的示例中,一个实例IBar
和将来可用,并且提供它的组件将:
有必要的依赖,
不参与任何无效的依赖循环,并且
在MEF的情况下,只提供一个实例.
在您给出的第二个示例中,构造函数转到IoC容器以检索其依赖项,您可以测试一个实例Foo
将能够使用应用程序的实际运行时配置正确实例化的唯一方法是实际构造它.
这在测试时具有各种尴尬的副作用,因为在运行时工作的代码不一定在测试工具下工作.Mocks不会这样做,因为真正的配置是我们需要测试的东西,而不是一些测试时间设置.
这个问题的根源是@Jon已经调出的差异:通过构造函数注入依赖项是声明性的,而第二个版本使用命令式服务定位器模式.
仔细使用IoC容器可以静态分析应用程序的运行时配置,而无需实际创建所涉及组件的任何实例.许多流行的容器提供了一些变化; Microsoft.Composition是针对.NET 4.5 Web和Metro风格应用程序的MEF版本,它提供了CompositionAssert
wiki文档中的示例.使用它,您可以编写如下代码:
// Whatever you use at runtime to configure the container var container = CreateContainer(); CompositionAssert.CanExportSingle(container);
(见这个例子).
通过在测试时验证应用程序的组合根,您可能会捕获一些错误,否则这些错误可能会在以后的过程中通过测试.
希望这是关于这个主题的这个全面的答案的一个有趣的补充!