346

这两种模式似乎都是控制反转原则的实现。也就是说,一个对象不应该知道如何构造它的依赖关系。

依赖注入 (DI) 似乎使用构造函数或设置器来“注入”它的依赖项。

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

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

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

  //...
}

Service Locator 似乎使用了一个“容器”,它连接了它的依赖关系并为 foo 提供了它的 bar。

使用服务定位器的示例:

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

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

  //...
}

因为我们的依赖关系只是对象本身,所以这些依赖关系有依赖关系,它们有更多的依赖关系,依此类推。于是,Inversion of Control Container(或DI Container)诞生了。示例:Castle Windsor、Ninject、Structure Map、Spring 等)

但是 IOC/DI 容器看起来与服务定位器完全一样。称它为 DI 容器是一个坏名字吗?IOC/DI 容器只是另一种类型的服务定位器吗?当我们有许多依赖项时,我们主要使用 DI 容器这一事实是否存在细微差别?

4

16 回答 16

217

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

于 2009-10-13T01:24:55.420 回答
108

当您使用服务定位器时,每个类都将依赖于您的服务定位器。这不是依赖注入的情况。依赖注入器通常只会在启动时被调用一次,以将依赖注入到某个主类中。这个主类所依赖的类将递归地注入它们的依赖关系,直到你有一个完整的对象图。

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

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

于 2009-10-13T01:22:55.783 回答
56

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

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

为客户端对象的接口指定前置条件和后置条件变得很困难,因为它的实现工作可能会被外部干预。

使用依赖注入,一旦指定了对象的依赖关系,它们就在对象本身的控制之下。

于 2009-10-13T01:26:00.877 回答
47

马丁福勒说

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

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

重要的原则是“取决于抽象,而不是具体”。这将使您的软件设计“松耦合”、“可扩展”、“灵活”。

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

你可以查看这篇文章:依赖倒置:服务定位器或依赖注入

也是经典:Martin Fowler 的控制容器反转和依赖注入模式

Ralph E. Johnson 和 Brian Foote设计可重用类

然而,让我大开眼界的是:ASP.NET MVC: Resolve or Inject?这就是问题所在……作者:Dino Esposito

于 2012-04-24T17:30:47.587 回答
27

使用构造函数 DI 的类向消费代码表明存在要满足的依赖关系。如果该类在内部使用 SL 来检索此类依赖项,则使用代码不会意识到这些依赖项。这可能表面上看起来更好,但实际上了解任何显式依赖关系是有帮助的。从建筑的角度来看,它更好。在进行测试时,您必须知道一个类是否需要某些依赖项,并配置 SL 以提供这些依赖项的适当假版本。使用 DI,只需传递假货。差别不大,但确实存在。

不过,DI 和 SL 可以一起工作。为公共依赖项(例如设置、记录器等)提供一个中心位置很有用。给定一个使用此类 deps 的类,您可以创建一个接收 deps 的“真实”构造函数,以及一个从 SL 检索并转发给“真实”构造函数的默认(无参数)构造函数。

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

于 2009-10-14T17:15:54.547 回答
17

两者都是 IoC 的实现技术。还有其他实现控制反转的模式:

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

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

主要区别在于依赖项的定位方式,在 Service Locator 中,客户端代码请求依赖项,在 DI Container 中,我们使用容器来创建所有对象,并将依赖项作为构造函数参数(或属性)注入。

于 2015-02-22T21:03:33.293 回答
8

在我的上一个项目中,我同时使用两者。我使用依赖注入来实现单元可测试性。我使用服务定位器来隐藏实现并依赖于我的 IoC 容器。是的!一旦你使用了一个 IoC 容器(Unity、Ninject、Windsor Castle),你就会依赖它。一旦它过时或出于某种原因如果您想要交换它,您将/可能需要更改您的实现 - 至少是组合根。但是服务定位器抽象了那个阶段。

您如何不依赖于您的 IoC 容器?要么您需要自己包装它(这是一个坏主意),要么您使用 Service Locator 配置您的 IoC 容器。因此,您将告诉服务定位器获取您需要的接口,它会调用配置为检索该接口的 IoC 容器。

在我的例子中,我使用ServiceLocator,它是一个框架组件。并将Unity用于 IoC 容器。如果将来我需要将我的 IoC 容器换成Ninject,我需要做的就是将我的服务定位器配置为使用 Ninject 而不是 Unity。轻松迁移。

这是一篇很棒的文章,解释了这种情况; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/

于 2015-08-03T19:33:13.033 回答
7

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

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

  • 有其所需的依赖关系,
  • 不参与任何无效的依赖循环,并且
  • 在 MEF 的情况下,只提供一个实例。

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

这在测试时会产生各种尴尬的副作用,因为将在运行时工作的代码不一定在测试工具下工作。模拟不会做,因为真正的配置是我们需要测试的东西,而不是一些测试时的设置。

这个问题的根源是@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<Foo>(container);

(见这个例子)。

通过在测试时验证应用程序的组合根,您可能会发现一些错误,否则这些错误可能会在稍后的过程中通过测试。

希望这是对该主题的其他全面答案集的有趣补充!

于 2012-07-03T05:13:25.073 回答
6

我认为这两者是一起工作的。

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

服务定位器的作用是将您的实现整合在一起。您在程序开始时通过一些引导程序设置服务定位器。引导是将一种实现类型与特定抽象/接口相关联的过程。在运行时为您创建。(基于您的配置或引导程序)。如果您没有实现依赖注入,那么使用服务定位器或 IOC 容器将非常困难。

于 2011-02-19T14:17:22.403 回答
5

注意:我没有完全回答这个问题。但是我觉得这对于依赖注入模式的新学习者来说很有用,他们对它与碰巧偶然发现这个页面的服务定位器(反)模式感到困惑。

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

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

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

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

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

于 2012-01-10T08:07:53.977 回答
5

遵循简单的概念让我对 Service Locator 和 DI Container 之间的区别有了更清晰的理解:

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

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

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

于 2019-11-14T08:11:23.557 回答
4

在这种过于简单的情况下,没有区别,它们可以互换使用。然而,现实世界的问题并不那么简单。假设 Bar 类本身有另一个名为 D 的依赖项。在这种情况下,您的服务定位器将无法解析该依赖项,您必须在 D 类中实例化它;因为实例化它们的依赖项是你的类的责任。如果 D 类本身有其他依赖项,情况会变得更糟,而在实际情况下,它通常会变得更加复杂。在这种情况下,DI 是比 ServiceLocator 更好的解决方案。

于 2013-04-02T03:15:20.417 回答
4

服务定位器和依赖注入都是遵循依赖倒置原则的对象访问模式实现


依赖注入是[静态/全局]对象访问模式

服务定位器是 [动态] 对象访问模式


如果您需要处理 [ ui 树] 或任何 [fractal 设计的应用程序]之类的 [动态结构],您可能需要 Service Locator。

例子:

  • React 的 createContext/useContext
  • 提供/注入 Vue
  • 角度提供者

如果您只想从您的类中获取一个不关心应用程序层次结构实例在该层次结构中的位置的实例,则应该使用 DI。

例子:

  • C#/Java 中的注解

当您在运行前不知道服务的实际提供者时,使用服务定位器。

当您知道它是提供该服务的静态容器时,就会使用 DI。


服务定位器模式更像是模块级别的依赖提供者,而 DI 是全局级别的。

当有一个子模块声明应该由它的父模块而不是静态解析类型(单例/瞬态/静态范围)提供的服务的依赖关系时,它非常有用。

它可以通过 DI 的范围注入模式来实现,而范围由应用程序的模块结构/关系定义。


个人建议:

  1. 尽可能使用 DI。
  2. 如果您必须处理分形结构中的动态/运行时服务解析,请使用服务定位器。
  3. 将 Service Locator 封装为作用域 DI,例如:@inject({ scope: 'contextual' })
  4. 通过接口而不是类/构造函数来定位服务。

详细信息:https ://docs.microsoft.com/zh-cn/dotnet/core/extensions/dependency-injection-guidelines#recommendations

于 2021-01-07T03:03:35.837 回答
1

依赖注入和服务定位器之间有什么区别(如果有的话)?这两种模式都擅长实现依赖倒置原则。服务定位器模式更容易在现有代码库中使用,因为它使整体设计更加松散,而无需强制更改公共接口。出于同样的原因,基于服务定位器模式的代码的可读性低于基于依赖注入的等效代码。

依赖注入模式清楚地说明了类(或方法)将具有哪些依赖项的签名。出于这个原因,生成的代码更干净,更易读。

于 2017-01-23T11:30:59.117 回答
0

DI 容器是服务定位器的超集。它可用于定位服务,并具有组装(连接)依赖注入的附加功能。

于 2020-04-21T20:30:31.430 回答
-3

作为记录

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

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

  //...
}

除非您真的需要一个接口(该接口被多个类使用),否则您不得使用它。在这种情况下,IBar 允许使用任何实现它的服务类。但是,通常,此接口将由单个类使用。

为什么使用接口是个坏主意?因为它真的很难调试。

例如,假设实例“bar”失败了,问题:哪个类失败了? 我应该修复哪个代码? 一个简单的视图,它通向一个接口,它就是我的路的尽头。

相反,如果代码使用硬依赖,那么很容易调试错误。

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

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

  //...
}

如果“bar”失败,那么我应该检查并启动类 BarService。

于 2018-09-30T00:47:21.287 回答