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

What's the difference between the Dependency Injection and Service Locator patterns?

这两种模式似乎都是控制反转原理的实现。也就是说,对象不应该知道如何构造其依赖项。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
//Foo Needs an IBar
public class Foo
{
  private IBar bar;

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

  //...
}

服务定位器似乎使用一个"容器",它将它的依赖性连接起来,并给出它的条。

使用服务定位器的示例:

1
2
3
4
5
6
7
8
9
10
11
12
//Foo Needs an IBar
public class Foo
{
  private IBar bar;

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

  //...
}

因为我们的依赖关系只是对象本身,所以这些依赖关系具有依赖关系,甚至具有更多的依赖关系,等等。这样,控制容器(或DI容器)的反转就诞生了。示例:城堡温莎、尼尼特、结构图、春天等)

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


这种差异可能看起来很小,但即使使用ServiceLocator,类仍然负责创建其依赖项。它只是使用服务定位器来完成。有了DI,类就有了依赖性。它既不知道,也不在乎它们来自何方。其中一个重要的结果是,DI示例更容易进行单元测试——因为您可以通过它的依赖对象的模拟实现。如果需要的话,可以将两者组合起来,并注入服务定位器(或工厂)。


当您使用服务定位器时,每个类都将依赖于您的服务定位器。依赖注入的情况并非如此。依赖注入器通常在启动时只调用一次,以将依赖项注入到某个主类中。这个主类所依赖的类将递归地注入它们的依赖项,直到有一个完整的对象图为止。

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

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


服务定位器隐藏依赖关系——通过查看对象是否到达数据库(例如,当它从定位器获得连接)时,您无法分辨。依赖关系注入(至少构造函数注入)依赖关系是显式的。

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

it becomes difficult to specify the pre and post
conditions for the client object's
interface, because the workings of its
implementation can be meddled with
from outside.

通过依赖注入,一旦指定了对象的依赖项,它们就受对象本身的控制。


马丁·福勒说:

With service locator the application class asks for it explicitly by a
message to the locator. With injection there is no explicit request,
the service appears in the application class – hence the inversion of
control.

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

重要的原则是"依靠抽象,而不是具体"。这将使您的软件设计"松散耦合"、"可扩展"、"灵活"。

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

您可以查看以下文章:依赖倒置:服务定位器或依赖注入

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

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

然而,打开我眼睛的是:asp.net mvc:解析还是注入?这是迪诺·埃斯波西托的问题


使用构造函数DI的类指示使用需要满足依赖关系的代码。如果类在内部使用SL来检索这些依赖项,则使用代码不知道这些依赖项。从表面上看,这似乎更好,但了解任何显式的依赖关系实际上是很有帮助的。从建筑的角度看,它更好。在进行测试时,您必须知道一个类是否需要某些依赖项,并配置SL来提供这些依赖项的适当的假版本。有了迪,就把假货传过来。不是很大的区别,但确实存在。

然而,DI和SL可以一起工作。有一个通用依赖项的中心位置(如设置、记录器等)很有用。给定一个使用此类deps的类,您可以创建一个接收deps的"real"构造函数,以及一个从sl检索并转发到"real"构造函数的默认(无参数)构造函数。

编辑:当然,当您使用SL时,您将向该组件引入一些耦合。这很讽刺,因为这种功能的想法是鼓励抽象和减少耦合。关注点可以是平衡的,这取决于您需要使用SL的地方有多少。如果按照上面的建议进行,就只在默认类构造函数中进行。


在我上一个项目中,我同时使用了这两种方法。我使用依赖注入来实现单元的可测试性。我使用服务定位器隐藏实现并依赖于我的IOC容器。是的!一旦你使用了一个IOC容器(Unity,Ninject,温莎城堡),你就可以依赖它了。一旦它过时了,或者出于某种原因,如果您想交换它,您将/可能需要更改您的实现——至少是composition-root。但是服务定位器抽象了这个阶段。

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

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

这里有一篇很好的文章解释了这个场景;http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/


两者都是国际奥委会的实施技术。还有其他实现控制反转的模式:

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

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

主要的区别在于依赖项的位置,在服务位置客户机代码请求依赖项,在DI中,我们使用容器创建所有对象,它将依赖项作为构造函数参数(或属性)注入。


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

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

  • 有其所需的依赖性,
  • 不参与任何无效的依赖循环,以及
  • 对于MEF,只提供一个实例。

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

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

这个问题的根源是@jon:injecting dependencies through the constructor已经调用的差异是声明性的,而第二个版本使用了命令式服务定位器模式。

当小心使用IOC容器时,可以静态分析应用程序的运行时配置,而不必实际创建任何相关组件的实例。许多流行的容器都提供了一些变化;Microsoft.Composition是面向.NET 4.5 Web和Metro风格应用程序的MEF版本,它在wiki文档中提供了一个CompositionAssert示例。使用它,您可以编写如下代码:

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

CompositionAssert.CanExportSingle<Foo>(container);

(参见本例)。

通过在测试时验证应用程序的组成根,您可能会捕获一些错误,否则这些错误可能会在流程的后面的测试中溜走。

希望这是一个有趣的除了这一套全面的答案的主题!


我想这两个是一起工作的。

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

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


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


注:我不完全回答这个问题。但是我觉得这对于依赖于注入模式的新学习者来说是很有用的,他们对服务定位器(反)模式感到困惑。

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

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

需要注意的是,您可能在DI构造函数中使用服务定位器对象,但您没有使用"服务定位器模式"。如果将其称为IOC容器对象,就不那么容易混淆了,正如您可能已经猜到的,它们本质上做了相同的事情(如果我错了,请纠正我)。

无论它被称为服务定位器(或仅仅定位器),还是IOC容器(或仅仅容器),都没有任何区别,正如您所猜测的,因为它们可能指的是相同的抽象(如果我错了,请纠正我)。仅仅是把它称为服务定位器就意味着一个人正在使用服务定位器反模式和依赖注入模式。

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


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

依赖注入模式使其变得清晰,因为一个类(或一个方法)将具有依赖性的签名。因此,生成的代码更清晰、更可读。


为了记录

1
2
3
4
5
6
7
8
9
10
11
12
//Foo Needs an IBar
public class Foo
{
  private IBar bar;

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

  //...
}

除非你真的需要一个接口(接口被多个类使用),否则你不能使用它。在这种情况下,IBA允许使用实现它的任何服务类。然而,通常,这个接口将由一个类使用。

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

例如,假设实例"bar"失败,问题:哪个类失败?.我应该修复哪个代码?一个简单的视图,它通向一个界面,它就在我的路的尽头。

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

1
2
3
4
5
6
7
8
9
10
11
12
//Foo Needs an IBar
public class Foo
{
  private BarService bar;

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

  //...
}

如果"酒吧"失败了,那么我应该检查一下,并为班级提供服务。