代码之家  ›  专栏  ›  技术社区  ›  Charles Graham

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

  •  251
  • Charles Graham  · 技术社区  · 16 年前

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

    依赖注入(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<IBar>();
      }
    
      //...
    }
    

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

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

    13 回复  |  直到 10 年前
        1
  •  218
  •   user2340612    7 年前

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

        2
  •  109
  •   Joel    16 年前

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

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

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

        3
  •  56
  •   Jeff Sternal    16 年前

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

    此外,服务定位器打破了封装,因为它们提供了对其他对象的依赖关系的全局访问点。使用服务定位器, as with any singleton :

    很难指定前置和后置 客户端对象的 接口,因为其 实施过程可能会受到干扰 从外面。

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

        4
  •  47
  •   Nathan    7 年前

    马丁·福勒州 :

    使用服务定位器,应用程序类通过 向定位器发送消息。对于注射,没有明确的要求, 服务出现在应用程序类中,因此 控制

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

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

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

    你可以查看以下帖子: Dependency Inversion: Service Locator or Dependency Injection

    也是经典: Inversion of Control Containers and the Dependency Injection pattern by Martin Fowler

    Designing Reusable Classes 拉尔夫·E·约翰逊;布莱恩·富特

    然而,让我大开眼界的是: ASP.NET MVC: Resolve or Inject? That’s the Issue… by Dino Esposito

        5
  •  27
  •   Grant Palin Bob King    16 年前

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

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

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

        6
  •  17
  •   Eliahu Aaron    6 年前

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

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

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

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

        7
  •  8
  •   cngzz1 Bob Cross    4 年前

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

    你怎么能不依赖你的IoC容器呢?要么你需要自己包装它(这是个坏主意),要么你使用服务定位器来配置你的IoC容器。因此,您将告诉服务定位器获取所需的接口,它将调用配置为检索该接口的IoC容器。

    就我而言,我使用 ServiceLocator ,这是一个框架组件。我用 Unity 我的IoC容器。如果将来我需要交换我的IoC容器 Ninject ,我只需要将我的服务定位器配置为使用Ninject而不是Unity。容易迁移。

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

        8
  •  7
  •   Yogesh Umesh Vaity VonC    5 年前

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

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

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

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

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

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

    如果小心使用,IoC容器可以静态地分析应用程序的运行时配置,而不会实际创建所涉及组件的任何实例。许多流行的容器提供了一些变化; 微软作文 ,这是MEF目标的版本。NET4.5Web和Metro风格的应用程序,提供 CompositionAssert wiki文档中的示例。使用它,您可以编写如下代码:

     // Whatever you use at runtime to configure the container
    var container = CreateContainer();
    
    CompositionAssert.CanExportSingle<Foo>(container);
    

    (见 this example ).

    通过验证 成分根 在测试时对应用程序进行测试,您可能会发现一些错误,否则这些错误可能会在稍后的测试过程中漏掉。

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

        9
  •  6
  •   NoelAdy    15 年前

    我认为这两种方法是协同工作的。

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

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

        10
  •  5
  •   Eliahu Aaron    6 年前

    以下简单的概念让我更清楚地理解了服务定位器和DI容器之间的区别:

    • 服务定位器 在消费者和it中使用 根据消费者的直接请求从某些存储中按ID提供服务

    • DI容器 位于外面的某个地方,它从一些存储和 推动 将它们传递给消费者(无论是通过构造函数还是通过方法)

    然而,我们只能在具体的消费者使用情况下讨论这两者之间的区别。在composition root中使用服务定位器和DI容器时,它们几乎是相似的。

        11
  •  5
  •   Yogesh Umesh Vaity VonC    5 年前

    注意:我并没有完全回答这个问题。但我觉得这对依赖注入模式的新学习者很有用,因为他们对它和 Service Locator (anti-)pattern 碰巧无意中发现了这一页。

    我知道服务定位器(现在似乎被认为是一种反模式)和依赖注入模式之间的区别,并且可以理解每个模式的具体示例,但是我被在构造函数中显示服务定位器的示例搞糊涂了(假设我们正在做构造函数注入)。

    “服务定位器”通常既用作模式的名称,也用作引用该模式中使用的对象(假设也是)的名称,以便在不使用新操作符的情况下获取对象。现在,同样类型的对象也可以在 composition root 执行依赖注入,这就是困惑所在。

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

    无论它被称为服务定位器(或仅仅是定位器),还是IoC容器(或仅仅是容器),正如您所猜测的,都没有区别,它们可能指的是同一个抽象(如果我错了,请纠正我)。只是,将其称为服务定位器表明,我们正在使用服务定位器反模式和依赖注入模式。

    IMHO将其命名为“locator”,而不是“location”或“locting”,有时也会让人认为文章中的服务定位器指的是服务定位器容器,而不是服务定位器(反)模式,尤其是当有一个称为依赖注入而非依赖注入的相关模式时。

        12
  •  4
  •   Daniel B    10 年前

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

        13
  •  4
  •   user5474476    5 年前

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


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

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


    如果你需要处理[ 动态结构 ]就像[ 用户界面树 ]或者你需要的任何应用程序。

    例子:

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

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

    例子:

    • C#/Java中的注释

    服务定位器在您不知道时使用 真正的提供者 在运行时之前对服务进行更新。

    当你知道它是 提供 那项服务。


    服务定位器模式更像是一个 模块级 依赖提供者,而DI是 全球层面 .

    当有一个 子模块 声明应该由其 父模块 而不是静态解析类型(singleton/transient/static scoped)。

    它可以通过 范围注射模式 范围由应用程序的模块结构/关系定义。


    个人建议:

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

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

        14
  •  1
  •   Yogesh    9 年前

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

    依赖项注入模式很清楚,因为类(或方法)将具有哪些依赖项的签名。因此,生成的代码更干净,可读性更强。

        15
  •  0
  •   Glenn Mohammad    6 年前

    DI容器是一个 超集 服务定位器。它可以用来 定位服务 ,具有额外的 组装(连接)依赖注入 .

        16
  •  -3
  •   magallanes    7 年前

    记录在案

    //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类。

    推荐文章