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

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

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

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

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


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


//...
}

Service Locator似乎使用了一个“容器”,它连接了它的依赖项并给了foo它的bar。

使用Service Locator的例子:

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


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


//...
}

因为我们的依赖关系只是对象本身,这些依赖关系有依赖关系,依赖关系有更多依赖关系,等等。因此,控制反转容器(或DI容器)诞生了。例如:Castle Windsor, Ninject, Structure Map, Spring等)

但是一个IOC/DI容器看起来像一个服务定位器。称它为DI容器是一个坏名字吗?IOC/DI容器只是服务定位器的另一个类型吗?当我们有很多依赖时,我们使用依赖注入容器,这是一个细微的差别吗?

94244 次浏览

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

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

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

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

服务定位器隐藏了依赖关系——例如,当一个对象从定位器获取连接时,你不能通过观察对象来判断它是否访问了数据库。使用依赖注入(至少是构造函数注入),依赖关系是显式的。

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

则很难指定前后 客户端对象的条件 接口,因为它的工作方式 可以对实现进行干预 从外面。< / p >

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

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

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

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

我认为这两者是相互作用的。

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

服务定位器的作用是将您的实现组合在一起。您可以在程序开始时通过一些引导绑定设置一个服务定位器。引导是将一种类型的实现与特定的抽象/接口相关联的过程。它在运行时为你创建。(根据您的配置或引导)。如果没有实现依赖注入,就很难利用服务定位器或IOC容器。

注意:我并不是在回答这个问题。但我觉得这对于那些对依赖注入模式和服务定位器(反)模式感到困惑的新学习者是有用的,他们碰巧偶然发现了这一页。

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

“服务Locator"通常既用作模式的名称,也用作该模式中使用的对象的名称(假设也是),以在不使用new操作符的情况下获取对象。现在,同一类型的对象也可以用于根组成来执行依赖注入,这就是产生混淆的地方。

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

无论它被称为服务定位器(或仅仅是定位器),还是IoC容器(或仅仅是容器),正如您所猜测的那样,它们都可能引用相同的抽象(如果我说错了,请纠正我)。只是称其为服务定位器表明将服务定位器反模式与依赖注入模式一起使用。

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

马丁·福勒说:

使用服务定位器,应用程序类通过显式地请求服务定位器 消息发送给定位器。对于注入,没有显式的请求, 服务出现在应用程序类中——因此是 控制。< / p >

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

重要的原则是“依赖抽象,而不是具象”。这将使你的软件设计“松散耦合”、“可扩展”、“灵活”。

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

你可以查看这个帖子:依赖倒置:服务定位器或依赖注入

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

设计可重用类 by Ralph E. Johnson &布莱恩·富特

然而,让我大开眼界的是:ASP。NET MVC:解析还是注入?这就是问题所在,迪诺·埃斯波西托著

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

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

  • 有必要的依赖关系,
  • 不涉及任何无效的依赖周期,以及
  • 在MEF的情况下,只提供一个实例。

在你给出的第二个例子中,构造函数去IoC容器检索它的依赖项,你可以测试Foo的实例能够被正确实例化的唯一方法是实际构建它

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

这个问题的根源是@Jon已经指出的区别:通过构造函数注入依赖是声明性的,而第二个版本使用命令式的Service Locator模式。

IoC容器,如果使用得当,可以静态地分析应用程序的运行时配置,而无需实际创建任何相关组件的实例。许多流行的容器提供了这方面的一些变化;微软。作文是针对。net 4.5 web和Metro风格应用程序的MEF版本,在wiki文档中提供了一个CompositionAssert示例。使用它,你可以编写如下代码:

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


CompositionAssert.CanExportSingle<Foo>(container);

(见这个例子)。

通过在测试时验证你的应用程序的根组成,你可以潜在地捕捉到一些错误,否则在以后的测试过程中可能会遗漏。

希望这是一个有趣的补充,否则这个主题的答案是全面的!

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

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

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

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

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

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

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

在我的例子中,我使用ServiceLocator,这是一个框架组件。我使用团结作为我的IoC容器。如果在未来我需要用Ninject交换我的IoC容器,我所需要做的就是配置我的服务定位器使用Ninject而不是Unity。容易迁移。

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

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

依赖注入模式从签名开始就明确了类(或方法)将具有哪些依赖项。因此,生成的代码更清晰,可读性更强。

郑重声明

//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;
}


//...
}

如果“酒吧”失败,那么我应该检查和杉木类BarService。

以下简单的概念让我更清楚地理解了Service Locator和DI Container的区别:

  • 服务定位器用于消费者,它通过直接消费者的请求从一些存储中按ID提取服务

  • DI容器位于外部某处,它从某个存储中获取服务,将服务推送给(无论是通过构造函数还是通过方法)

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

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

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


依赖注入是[static/global]对象访问模式

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


如果你需要处理[动态结构]像[ui树]或任何[分形设计的应用程序],你可能需要服务定位器。

例子:

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

如果你只想从你的类中获得不要关心应用程序的层次结构实例在该层次结构中的位置的实例,你应该使用DI。

例子:

  • c# /Java中的注释

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

当你知道服务是静态容器,提供时使用DI。


服务定位器模式更像模块级依赖提供程序,而DI模式是全球层面上

当有sub-module声明服务的依赖关系时,它非常有用,该服务应该由它的parent-module提供,而不是静态解析类型(单例/瞬态/静态作用域)。

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


个人建议:

  1. 尽可能使用DI。
  2. 如果你必须处理分形结构中的动态/运行时服务解析,请使用Service Locator。
  3. 将服务定位符封装为一个有作用域的DI,例如:
  4. 通过接口定位服务,而不是通过类/构造函数。

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