依赖注入和服务定位器模式之间有什么区别?

Cha*_*ham 271 design-patterns dependency-injection service-locator

这两种模式看起来都像是控制反转原理的实现.也就是说,一个对象不应该知道如何构造它的依赖关系.

依赖注入(DI)似乎使用构造函数或setter来"注入"它的依赖项.

使用构造函数注入的示例:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}
Run Code Online (Sandbox Code Playgroud)

服务定位器似乎使用了一个"容器",它连接了它的依赖关系并给它foo吧.

使用服务定位器的示例:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}
Run Code Online (Sandbox Code Playgroud)

因为我们的依赖项只是对象本身,所以这些依赖项具有依赖项,它们具有更多依赖项,依此类推.因此,控制容器的反转(或DI容器)诞生了.示例:Castle Windsor,Ninject,Structure Map,Spring等)

但是,IOC/DI容器看起来完全相同像一个服务定位器.将它称为DI容器是一个坏名字?IOC/DI容器只是另一种服务定位器吗?当我们有很多依赖关系时,我们使用DI容器这一事实的细微差别是什么?

tva*_*son 165

差异可能看起来很小,但即使使用ServiceLocator,该类仍然负责创建其依赖项.它只是使用服务定位器来完成它.使用DI,该类被赋予其依赖性.它既不知道,也不关心它们来自何处.这样做的一个重要结果是DI示例更容易进行单元测试 - 因为您可以将其依赖对象的模拟实现传递给它.如果需要,您可以将两者结合起来 - 并注入服务定位器(或工厂).

  • 此外,您可以在构造类时使用它们.默认构造函数可以使用SL来检索依赖项,并将它们传递给接收这些依赖项的"真实"构造函数.你可以获得两全其美的效果. (18认同)
  • 不,ServiceLocator是负责实例化给定依赖项(插件)的正确实现的人.在DI的情况下,DI"容器"是负责的. (6认同)
  • @Rogerio是的,但是班级仍然需要知道服务定位器...这是两个权限.我经常看到Service Locator委托给DI容器进行查找,尤其是需要服务支持的瞬态对象. (4认同)
  • "这样做的一个重要结果是DI示例更容易进行单元测试 - 因为你可以传递它依赖对象的模拟实现." 不对.在单元测试中,可以使用服务定位器容器中的寄存器功能调用来轻松地向注册表添加模拟. (3认同)
  • @Adam我没有说服务定位器会委托给DI容器.这些是两个相互排斥的模式,如["官方"文章](http://martinfowler.com/articles/injection.html)中所述.对我来说,Service Locator在实践中比DI具有巨大的优势:使用DI容器会引起滥用(我反复看到过),而使用服务定位器却没有. (2认同)
  • @Rogerio我有完全相反的经历,并且看到服务定位器完全被滥用,特别是如果你在更少的面向对象和更多的行为和状态分离的情况下编写更多的组件/服务方向.服务定位器模式非常适合喜欢传统面向对象编程的人(例如Active Record模式与DAO/Repository模式).在过去,这并没有特别适用于消息传递架构.你必须向我展示滥用的证据. (2认同)
  • @Adam关于DI滥用的证据,我不知道任何公开可用的代码库.在我见过或参与过的商业应用程序(Java和C#.NET)中,DI的使用显然只是出于政治原因(有些公司甚至要求*所有新项目使用一些DI容器).实际上有数百个组件具有单独的接口,但只有一个实现,并没有真正期望其他实现会出现.根据我的经验,这似乎在IT行业很常见,但也许你比我更幸运. (2认同)
  • @Drumbeg - 好的,现在将其更改为使用不同的定位器或更改定位器的注册方式。看看服务定位器有问题 - 您已经将实现和测试耦合到类实际工作不需要的东西,只是它的配置。 (2认同)

Joe*_*oel 83

使用服务定位器时,每个类都将依赖于服务定位器.依赖注入不是这种情况.依赖注入器通常在启动时仅调用一次,以将依赖项注入某个主类.这个主类所依赖的类将递归地注入它们的依赖项,直到你有一个完整的对象图.

一个很好的比较:http://martinfowler.com/articles/injection.html

如果您的依赖注入器看起来像服务定位器,类直接调用注入器,它可能不是依赖注入器,而是服务定位器.

  • 但是,如何处理在运行时必须创建对象的情况?如果使用"new"手动创建它们,则无法使用DI.如果你打电话给DI框架寻求帮助,你就会破坏模式.那剩下什么选择呢? (16认同)
  • @Boris我有同样的问题,并决定注入特定类的工厂.不是很漂亮,但完成了工作.很想看到更漂亮的解决方案. (5认同)
  • "依赖于你的服务定位器",仅此一点就是+1 (2认同)
  • @Boris 如果我需要动态构建新对象,我会为所述对象注入一个抽象工厂。这类似于在此实例中注入服务定位器,但提供了一个具体的、统一的、编译时的接口,用于构建相关对象并使依赖关系明确。 (2认同)

Jef*_*nal 47

服务定位器隐藏依赖关系 - 当从对象获取连接时,您无法通过查看对象是否访问数据库(例如).使用依赖项注入(至少构造函数注入),依赖项是显式的.

此外,服务定位器打破了封装,因为它们提供了对其他对象的依赖关系的全局访问点.使用服务定位器,与任何单例一样:

为客户端对象的接口指定前置和后置条件变得很困难,因为其实现的工作可以从外部进行干预.

使用依赖项注入,一旦指定了对象的依赖项,它们就会受到对象本身的控制.

  • 我更喜欢"单身人士认为愚蠢",http://steve.yegge.googlepages.com/singleton-considered-stupid (3认同)
  • 我喜欢'Steve Yegge,那篇文章的标题很棒,但我认为我引用的文章和MiškoHevery的"单身人士是病态的骗子"(http://misko.hevery.com/2008/08/17/singletons- is-pathological-liars /)对服务定位器的特定恶魔提出了更好的理由. (2认同)

Nat*_*han 36

Martin Fowler说:

使用服务定位器,应用程序类通过发送给定位器的消息明确地请求它.使用注入没有明确的请求,服务出现在应用程序类中 - 因此控制反转.

简而言之:服务定位器和依赖注入只是依赖性倒置原则的实现.

重要的原则是"取决于抽象,而不是取决于具体结果".这将使您的软件设计"松散耦合","可扩展","灵活".

您可以使用最适合您需求的那个.对于具有庞大代码库的大型应用程序,您最好使用服务定位器,因为依赖注入需要对代码库进行更多更改.

您可以查看以下帖子:依赖性反转:服务定位器或依赖注入

经典之作:控制容器的反转和依赖注入模式由Martin Fowler提供

由Ralph E. Johnson和Brian Foote 设计可重复使用的课程

然而,让我眼前一亮的是:ASP.NET MVC:Resolve或Inject?那就是Dino Esposito的问题......


Gra*_*lin 21

使用构造函数DI的类指示消耗代码以满足依赖性.如果类在内部使用SL来检索此类依赖项,则使用代码不会识别依赖项.从表面上看,这似乎更好,但知道任何明确的依赖关系实际上是有帮助的.从架构的角度来看,它更好.在进行测试时,您必须知道某个类是否需要某些依赖项,并配置SL以提供这些依赖项的适当虚假版本.使用DI,只需传入假货.没有太大的区别,但它确实存在.

但是,DI和SL可以协同工作.具有公共依赖关系的中心位置(例如设置,记录器等)是有用的.给定使用此类deps的类,您可以创建一个接收deps的"真实"构造函数,以及一个从SL检索并转发到"real"构造函数的默认(无参数)构造函数.

编辑:当然,当你使用SL时,你会引入一些与该组件的耦合.这是具有讽刺意味的,因为这种功能的想法是鼓励抽象并减少耦合.问题可以平衡,这取决于您需要使用SL的地方数量.如果按照上面的建议完成,只需在默认的类构造函数中完成.


Nin*_*nea 7

它们都是IoC的实现技术.还有其他模式实现控制反转:

  • 工厂模式
  • 服务定位器
  • 依赖注入(构造函数注入,参数注入(如果不需要),接口注入的setter注入)......

服务定位器和DI看起来更相似,它们都使用容器来定义依赖关系,它将抽象映射到具体实现.

主要区别在于依赖关系是如何定位的,在Service Location客户端代码中请求依赖关系,在DI中我们使用容器来创建所有对象,并且它将依赖关系注入构造函数参数(或属性).


Teo*_*ahi 7

在我的上一个项目中,我使用了两者 我使用依赖注入来实现单元可测试性.我使用服务定位器来隐藏实现并依赖于我的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/


Noe*_*Ady 6

我认为这两个一起工作.

依赖注入意味着您将一些依赖的类/接口推送到消费类(通常是它的构造函数).这通过接口将两个类分离,这意味着消费类可以使用许多类型的"注入依赖"实现.

服务定位器的作用是将您的实现整合在一起.您可以在程序开始时通过一些引导捆绑设置服务定位器.Bootstrapping是将一种实现与特定抽象/接口相关联的过程.这是在运行时为您创建的.(基于你的配置或引导程序).如果您尚未实现依赖注入,则使用服务定位器或IOC容器将非常困难.


Nic*_*rdt 6

添加的一个原因,受到我们上周为MEF项目编写的文档更新的启发(我帮助构建MEF).

一旦应用程序由可能数千个组件组成,就很难确定是否可以正确地实例化任何特定组件.通过"正确实例化",我的意思是在这个基于Foo组件的示例中,一个实例IBar和将来可用,并且提供它的组件将:

  • 有必要的依赖,
  • 不参与任何无效的依赖循环,并且
  • 在MEF的情况下,只提供一个实例.

在您给出的第二个示例中,构造函数转到IoC容器以检索其依赖项,您可以测试一个实例Foo将能够使用应用程序的实际运行时配置正确实例化的唯一方法是实际构造它.

这在测试时具有各种尴尬的副作用,因为在运行时工作的代码不一定在测试工具下工作.Mocks不会这样做,因为真正的配置是我们需要测试的东西,而不是一些测试时间设置.

这个问题的根源是@Jon已经调出的差异:通过构造函数注入依赖项是声明性的,而第二个版本使用命令式服务定位器模式.

仔细使用IoC容器可以静态分析应用程序的运行时配置,而无需实际创建所涉及组件的任何实例.许多流行的容器提供了一些变化; Microsoft.Composition是针对.NET 4.5 Web和Metro风格应用程序的MEF版本,它提供了CompositionAssertwiki文档中的示例.使用它,您可以编写如下代码:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);
Run Code Online (Sandbox Code Playgroud)

(见这个例子).

通过在测试时验证应用程序的组合根,您可能会捕获一些错误,否则这些错误可能会在以后的过程中通过测试.

希望这是关于这个主题的这个全面的答案的一个有趣的补充!


bli*_*sta 5

注意:我并没有完全回答这个问题。但是我觉得这对于刚接触这个页面的依赖注入模式的新学习者很有用,他们对它与服务定位器(反)模式混淆了。

我知道服务定位器(现在似乎被视为反模式)和依赖注入模式之间的区别,并且可以理解每种模式的具体示例,但我对在构造函数中显示服务定位器的示例感到困惑(假设我们重新进行构造函数注入)。

“服务定位器”通常既用作模式的名称,也用作引用该模式中使用的对象(假设也是)的名称,以在不使用 new 运算符的情况下获取对象。现在,同样类型的对象也可以用于组合根来执行依赖注入,这就是混淆的来源。

需要注意的一点是,您可能在 DI 构造函数中使用了服务定位器对象,但您没有使用“服务定位器模式”。如果将其称为 IoC 容器对象,则不会那么令人困惑,因为您可能已经猜到它们基本上做同样的事情(如果我错了,请纠正我)。

无论是被称为服务定位器(或只是定位器),还是被称为 IoC 容器(或只是容器)都没有你猜到的区别,它们可能指的是相同的抽象(如果我错了,请纠正我)。只是将其称为服务定位器意味着将服务定位器反模式与依赖注入模式一起使用。

恕我直言,将其命名为“定位器”而不是“位置”或“定位”,也会导致人们有​​时认为文章中的服务定位器指的是服务定位器容器,而不是服务定位器(反)模式,尤其是当有一种称为依赖注入而不是依赖注入的相关模式时。


小智 5

遵循简单的概念让我更清楚地了解 Service Locator 和 DI Container 之间的区别:

  • 服务定位器在消费者中使用,它根据消费者的直接请求从某个存储中按 ID提取服务

  • DI Container位于外面的某个地方,它从一些存储中获取服务并将它们送给消费者(无论是通过构造函数还是通过方法)

但是,我们只能在具体的消费者使用情况下谈论它们之间的区别。当Service Locator和DI Container用于组合根时,它们几乎相似。