题 什么是依赖注入?


已经发布了几个有关具体问题的问题 依赖注入比如何时使用它以及它的框架是什么。然而,

什么是依赖注入以及何时/为什么应该或不应该使用它?


2635
2017-09-25 00:28


起源


请参阅我对依赖注入的讨论 这里。 - Kevin S.
我同意有关链接的评论。我可以理解你可能想引用其他人。但至少要添加为什么你要链接它们以及什么使得这个链接比我通过谷歌获得的其他链接更好 - Christian Payne
@AR:技术上,依赖注入是 不 一种特殊形式的IoC。相反,IoC是一种用于提供依赖注入的技术。其他技术可用于提供依赖注入(尽管IoC是唯一常用的技术),IoC也用于许多其他问题。 - Sean Reilly
如果你不总结它,我们不知道它值得努力去阅读它 - Casebash
关于链接,请记住它们经常以这种或那种方式消失。在SO答案中有越来越多的死链接。所以,无论链接文章有多好,如果你找不到它就没有用。 - DOK


答案:


基本上,不是让对象创建依赖项或要求工厂对象为它们创建一个依赖项,而是将所需的依赖项传递给外部对象,并使其成为别人的问题。这个“某人”要么是依赖图之上的对象,要么是构建依赖图的依赖注入器(框架)。我在这里使用它的依赖是当前对象需要持有引用的任何其他对象。

依赖注入的一个主要优点是它可以使测试更容易。假设您有一个对象,在其构造函数中执行以下操作:

public SomeClass() {
    myObject = Factory.getObject();
}

当你想要做的只是在SomeClass上运行一些单元测试时,这可能很麻烦,特别是如果myObject是复杂磁盘或网络访问的话。所以现在你正在考虑模仿myObject,但也以某种方式拦截工厂调用。硬。相反,将对象作为参数传递给构造函数。现在你已将问题转移到其他地方,但测试可以变得更容易。只需创建一个虚拟myObject并将其传入。构造函数现在看起来有点像:

public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

这是一种依赖注入方式 - 通过构造函数。有几种机制是可能的。

  • 正如评论中所指出的,一种常见的替代方法是定义一个无操作的构造函数,并通过属性设置器(h / t @MikeVella)注入依赖项。
  • 马丁福勒 记录第三种替代方法(h / t @MarcDix),其中类显式实现了它们希望注入的依赖项的接口。

当不使用依赖注入时(例如在其构造函数中执行过多工作的类等),在单元测试中隔离组件往往变得更加困难。

早在2013年,当我写这个答案时,这是一个主题 谷歌测试博客。这对我来说仍然是最大的优势,因为您可能并不总是需要在运行时设计中具有额外的灵活性(例如,对于服务定位器或类似的模式),但是您通常需要能够在测试期间隔离您的类。


1638
2017-09-25 00:49



承认Ben Hoffstein对Martin Fowler的文章的引用是必要的,因为它指的是关于这个主题的“必读”,我接受了wds的回答,因为它实际上回答了SO的问题。 - AR.
+1解释和动机: 创建一个类依赖别人问题的对象。另一种说法是,DI使课程更具凝聚力(他们的责任更少)。 - Fuhrmanator
你说依赖关系是“传入构造函数”,但据我所知,这并不严格。如果在实例化对象后将依赖项设置为属性,它仍然是依赖注入,对吗? - Mike Vella
@MikeVella是的,这是正确的。在大多数情况下,它没有什么区别,尽管属性通常更灵活一些。我将略微编辑文本以指出这一点。 - wds
到目前为止我找到的最好的答案之一,因此我真的很有兴趣改进它。它缺少对第三种依赖注入形式的描述: 接口注入。 - Marc Dix


到目前为止我发现的最佳定义是 一个是詹姆斯肖尔

“依赖注入”是一个25美元   5美分概念的术语。 [...]   依赖注入意味着给予   对象的实例变量。 [...]。

马丁福勒的一篇文章 这可能也很有用。

依赖注入基本上是提供对象所需的对象(它的依赖关系),而不是让它自己构造它们。它是一种非常有用的测试技术,因为它允许模拟或删除依赖项。

可以通过多种方式(例如构造函数注入或setter注入)将依赖项注入到对象中。甚至可以使用专门的依赖注入框架(例如Spring)来做到这一点,但它们当然不是必需的。您不需要这些框架具有依赖注入。显式地实例化和传递对象(依赖关系)与框架注入一样好。


2063
2017-09-26 16:50



我喜欢詹姆斯文章的解释,尤其是结尾:“不过,你必须惊叹于任何采用三个概念的方法('TripPlanner,'CabAgency'和'AirlineAgency'),将它们变成9 +类,然后在编写一行应用程序逻辑之前添加几十行胶水代码和配置XML。“这是我经常看到的(遗憾的是) - 依赖注入(由他解释本身很好)被滥用于过度复杂本来可以做得更容易的事情 - 最终编写“支持”代码...... - Matt
Re:“显式地实例化和传递对象(依赖关系)与框架注入一样好。”那么为什么人们制作框架呢? - dzieciou
出于同样的原因,每个框架都得到(或者至少应该得到):因为有很多重复/样板代码需要在达到某种复杂性时编写。即使不是严格需要,人们也会多次遇到这个问题。 - Thiago Arrais
这应该是正确的答案..或“依赖注入意味着'传递对象引用'” - Hal50000
5美分概念的25美元期限已经到期。这是一篇很好的文章,它帮助了我: codeproject.com/Articles/615139/... - Hill


我发现这个有趣的例子 松耦合

任何应用程序都由许多对象组成,这些对象彼此协作以执行一些有用的东西。传统上,每个对象都负责获取它自己对与之协作的依赖对象(依赖项)的引用。这导致高度耦合的类和难以测试的代码。

例如,考虑一个 Car 目的。

一个 Car 运行取决于车轮,发动机,燃料,电池等。传统上,我们定义这些依赖对象的品牌以及对象的定义 Car 目的。

没有依赖注入(DI):

class Car{
  private Wheel wh = new NepaliRubberWheel();
  private Battery bt = new ExcideBattery();

  //The rest
}

在这里, Car 目的 负责创建依赖对象。

如果我们想要改变其依赖对象的类型怎么办 - 比方说 Wheel  - 最初之后 NepaliRubberWheel() 穿刺? 我们需要重新创建具有新依赖性的Car对象 ChineseRubberWheel(),但只有 Car 制造商可以做到这一点

然后是什么 Dependency Injection 为...做我们?

使用依赖项注入时,会为对象提供依赖项 在运行时而不是编译时间(汽车制造时间)。 这样我们现在就可以改变了 Wheel 随时随地。在这里, dependency (wheel)可以注入 Car 在运行时。

使用依赖注入后:

我们到了 注射 该 依赖 (车轮和电池)在运行时。因此这个词: 依赖注入。 

class Car{
  private Wheel wh = [Inject an Instance of Wheel (dependency of car) at runtime]
  private Battery bt = [Inject an Instance of Battery (dependency of car) at runtime]
  Car(Wheel wh,Battery bt) {
      this.wh = wh;
      this.bt = bt;
  }
  //Or we can have setters
  void setWheel(Wheel wh) {
      this.wh = wh;
  }
}

资源: 理解依赖注入


507
2018-05-22 04:01



我理解这一点的方式是,不是将新对象实例化为另一个对象的一部分,而是在需要时注入所述对象,从而删除第一个对象对它的依赖。是对的吗? - JeliBeanMachine
我在这里用咖啡店的例子描述了这个:digigene.com/design-patterns/dependency-injection-coffeeshop - Ali Nem
真的很喜欢这个类比,因为它是简单的英语,使用一个简单的类比。说我是丰田,已经花了太多的经济和人力来制造汽车从设计到滚动装配线,如果有现有信誉良好的轮胎生产商,我为什么要从头开始制造轮胎制造部门,即 new 轮胎?我不。我所要做的就是从他们那里购买(通过param注入),安装和哇!所以,回到编程,说一个C#项目需要使用现有的库/类,有两种方法可以运行/调试,1 - 添加对整个项目的引用 - Jeb50
(续),..外部库/类,或者从DLL中添加它。除非我们必须查看此外部类的内容,否则将其添加为DLL是一种更简单的方法。因此选项1是 new 它,选项2将其作为参数传递给它。可能不准确,但简单愚蠢容易理解。 - Jeb50
奇妙的解释, - Avan


依赖注入是一种实践,其中对象的设计方式是从其他代码段接收对象的实例,而不是在内部构造它们。这意味着可以替换实现对象所需的接口的任何对象而无需更改代码,这简化了测试并改善了解耦。

例如,考虑以下条款:

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

在这个例子中,执行 PersonService::addManager 和 PersonService::removeManager 需要GroupMembershipService的一个实例才能完成它的工作。如果没有依赖注入,传统的方法是实例化一个新的 GroupMembershipService 在构造函数中 PersonService 并在两个函数中使用该实例属性。但是,如果构造函数 GroupMembershipService 它需要多个东西,或者更糟糕的是,有一些初始化的“setter”需要在它上面调用 GroupMembershipService,代码增长相当快,而且 PersonService现在不仅取决于 GroupMembershipService 还有其他一切 GroupMembershipService 依赖于取决于。而且,联系到 GroupMembershipService 是硬编码的 PersonService 这意味着你不能“假装”了 GroupMembershipService 用于测试目的,或在应用程序的不同部分使用策略模式。

使用依赖注入,而不是实例化 GroupMembershipService 在你的 PersonService,你要么把它传递给 PersonService 构造函数,或者添加一个Property(getter和setter)来设置它的本地实例。这意味着你的 PersonService 再也不用担心如何创建了 GroupMembershipService,它只接受它给出的那些,并与它们一起工作。这也意味着任何属于它的子类 GroupMembershipService,或实现 GroupMembershipService 界面可以“注入” PersonService,和 PersonService 不需要知道变化。


233
2017-09-25 06:49



如果你能在使用DI之后提供相同的代码示例,那本来会很棒 - CodyBugstein
我希望python有这样的东西。现在我们必须模拟一切进行测试。 - user2601010
你当然可以 做 使用Python进行依赖注入,但许多大型库(Django等)都没有,因为python中的模拟库非常可靠。当你使用DI时,你仍然需要编写模拟,它更容易。 - Adam Ness


接受的答案是一个很好的答案 - 但我想补充一点,DI非常类似于代码中经典的避免硬编码常量。

当您使用某个常量(如数据库名称)时,您可以快速将其从代码内部移动到某个配置文件,并将包含该值的变量传递到需要它的位置。这样做的原因是这些常量通常比其余代码更频繁地更改。例如,如果您想测试测试数据库中的代码。

DI在面向对象编程领域类似于此。那里的值而不是常量文字是完整的对象 - 但是从类代码中移出代码的原因是相似的 - 对象的更改频率比使用它们的代码更频繁。需要进行此类更改的一个重要案例是测试。


142
2018-01-06 18:33



+1“对象更频繁地更改使用它们的代码”。为了概括,在通量点处添加间接。根据不同的名称,取决于不同的名称! - Chethan
“非常类似于代码中经典的避免硬编码常量” - dsdsdsdsd


让我们想象你想钓鱼:

  • 没有依赖注入,你需要自己处理所有事情。你需要找一条船,买一根钓竿,寻找诱饵等等。当然,这可能会给你带来很多责任。在软件方面,这意味着您必须对所有这些事情执行查找。

  • 通过依赖注入,其他人负责所有准备工作并为您提供所需的设备。您将收到(“注入”)船,钓竿和鱼饵 - 所有这些都可以使用。


96
2017-10-22 04:47



另一方面,想象一下你雇用一名管道工来重做你的浴室,然后他说:“太棒了,这里有我需要你找到的工具和材料清单”。这不应该是水管工的工作吗? - Josh Caswell
所以有人需要照顾一些没有业务知道的人......但仍然决定收集船,棍子和诱饵的清单 - 尽管准备使用。 - Chookoos
@JoshCaswell不,这将是水管工的工作者的工作。作为客户,您需要完成管道工程。为此你需要一个水管工。水管工需要它的工具。为了获得这些,它由管道公司配备。作为客户,您不想确切知道管道工的工作或需要。作为水管工你知道你需要什么,但你只想做你的工作,而不是得到一切。作为管道工的雇主,您有责任在将管道工送到人们家之前为他们提供所需的装备。 - kai
@kai我理解你的观点。在软件中我们谈的是工厂,对吗?但DI也通常意味着该类不使用工厂,因为仍未注入。您,客户,需要联系雇主(工厂)为您提供工具,以便您可以转移到水管工。这不是它在程序中实际工作的方式吗?因此,虽然客户(调用类/功能/无论如何)不必采购工具,但他们仍然必须是中间人,确保他们从雇主(工厂)到管道工(注入类)。 - KingOfAllTrades
@KingOfAllTrades:当然,在某些时候你必须有人雇用和装备水管工,或者你没有管道工。但是你没有客户这样做。顾客只需要一个水管工,并让一个人已经配备了他需要做的工作。使用DI,您最终仍然需要一些代码来实现依赖关系。但是你将它与实际工作的代码分开。如果你把它放到最大程度上,你的对象就会知道它们的依赖关系,并且对象图形构建在外面发生,通常在init代码中。 - cHao


这个 是最简单的解释 依赖注入 和 依赖注入容器 我见过:

没有依赖注入

  • 应用程序需要Foo(例如控制器),因此:
  • 应用程序创建Foo
  • 应用程序调用Foo
    • Foo需要Bar(例如服务),所以:
    • Foo创造了Bar
    • Foo打电话给Bar
      • Bar需要Bim(服务,存储库, ......),所以:
      • 酒吧创造了Bim
      • 酒吧做点什么

使用依赖注入

  • 应用程序需要Foo,需要Bar,需要Bim,所以:
  • 应用程序创建Bim
  • 应用程序创建Bar并将其赋予Bim
  • 应用程序创建Foo并为其提供Bar
  • 应用程序调用Foo
    • Foo打电话给Bar
      • 酒吧做点什么

使用依赖注入容器

  • 应用需要Foo所以:
  • 应用程序从Container获取Foo,因此:
    • 容器创造了Bim
    • Container创建Bar并将其赋予Bim
    • 容器创建Foo并给它Bar
  • 应用程序调用Foo
    • Foo打电话给Bar
      • 酒吧做点什么

依赖注入 和 依赖注入容器 有不同的东西:

  • 依赖注入是一种编写更好代码的方法
  • DI容器是一种帮助注入依赖关系的工具

您不需要容器来执行依赖项注入。但是容器可以帮助您。


79
2018-05-05 11:53



@Trix使用依赖注入是否合适? - roottraveller
@rootTraveller谷歌是你的朋友: 什么时候不适合使用依赖注入模式? - Trix


让我们试试一下简单的例子吧 汽车 和 发动机 班级,任何汽车都需要发动机才能到达任何地方,至少现在如此。所以下面代码看起来如何没有依赖注入。

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

为了实例化Car类,我们将使用下一个代码:

Car car = new Car();

我们与GasEngine紧密耦合的代码问题,如果我们决定将其更改为ElectricityEngine,那么我们将需要重写Car类。应用程序越大,我们将需要添加和使用新型引擎的问题和头痛就越多。

换句话说,这种方法是我们的​​高级Car类依赖于较低级别的GasEngine类,它违反了SOLID的依赖性倒置原则(DIP)。 DIP建议我们应该依赖于抽象,而不是具体的课程。所以为了满足这个要求,我们引入了IEngine接口和重写代码,如下所示:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

现在我们的Car类只依赖于IEngine接口,而不是特定的引擎实现。 现在,唯一的技巧是如何创建Car的实例并为其提供一个实际的具体Engine类,如GasEngine或ElectricityEngine。那是在哪里 依赖注入 进来。

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

这里我们基本上将我们的依赖项(Engine实例)注入(传递)到Car构造函数。所以现在我们的类在对象及其依赖项之间存在松耦合,我们可以轻松添加新类型的引擎而无需更改Car类。

主要的好处是 依赖注入 这些类更松散耦合,因为它们没有硬编码的依赖项。这遵循上面提到的依赖性倒置原则。类不是引用特定的实现,而是请求抽象(通常 接口)在课程建设时提供给他们的。

所以最后 依赖注入 只是一种技术   实现对象及其依赖项之间的松散耦合。   而不是直接实例化类所需的依赖项   为了执行其操作,将依赖关系提供给类   (最常见的)通过构造函数注入。

此外,当我们有很多依赖项时,使用Inversion of Control(IoC)容器是非常好的做法,我们可以告诉哪些接口应该映射到我们所有依赖项的哪些具体实现,并且我们可以让它在构造时为我们解析这些依赖项我们的目标。例如,我们可以在IoC容器的映射中指定 IEngine 依赖应该映射到 GasEngine 当我们向IoC容器询问我们的实例时 汽车 类,它会自动构造我们的 汽车 有一个班级 GasEngine 依赖传入。

更新: 最近看过Julie Lerman关于EF Core的课程,也喜欢她关于DI的简短定义。

依赖注入是一种允许应用程序注入的模式   将对象移动到需要它们的类,而不强制它们   要负责这些对象的类。它允许您的代码   更松散耦合,实体框架核心插入到这一点   服务体系。


77
2017-07-06 09:42



很好的解释,为什么这没有得到任何票? - nhoxbypass
这应该是最好的答案之一。 - Shwetabh Shekhar
出于好奇,这与战略模式有何不同?这种模式封装了算法并使它们可以互换。感觉依赖注入和策略模式非常相似。 - elixir


“依赖注入”不仅仅意味着使用参数化构造函数和公共setter吗?

James Shore的文章显示了以下用于比较的示例

没有依赖注入的构造函数:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example() { 
    myDatabase = new DatabaseThingie(); 
  } 

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
} 

具有依赖注入的构造函数:

public class Example { 
  private DatabaseThingie myDatabase; 

  public Example(DatabaseThingie useThisDatabaseInstead) { 
    myDatabase = useThisDatabaseInstead; 
  }

  public void doStuff() { 
    ... 
    myDatabase.getData(); 
    ... 
  } 
}

45
2018-05-02 00:40



当然在DI版本中你不想在no参数构造函数中初始化myDatabase对象?似乎没有意义,如果你试图在不调用重载构造函数的情况下调用DoStuff,那么会抛出异常吗? - Matt Wilko
除非 new DatabaseThingie() 不生成有效的myDatabase实例。 - JaneGoodall


什么是依赖注入(DI)?

正如其他人所说, 依赖注入(DI) 消除了我们感兴趣的类(消费者类)所依赖的其他对象实例的直接创建和生命管理的责任(在 UML感)。这些实例通常作为构造函数参数或通过属性设置器传递给我们的使用者类(依赖对象实例化和传递给使用者类的管理通常由 控制反转(IoC) 容器,但这是另一个话题)。

DI,DIP和SOLID

具体来说,在Robert C Martin的范例中 面向对象设计的SOLID原则DI 是可能的实现之一 依赖倒置原则(DIP)。该 DIP是 D 的 SOLID 口头禅   - 其他DIP实现包括服务定位器和插件模式。

DIP的目标是解耦类之间紧密的,具体的依赖关系,而是通过抽象来放松耦合,这可以通过一个抽象来实现。 interfaceabstract class 要么 pure virtual class,取决于使用的语言和方法。

如果没有DIP,我们的代码(我称之为“消费类”)直接耦合到具体的依赖关系,并且通常还有责任知道如何获取和管理这种依赖的实例,即概念上:

"I need to create/use a Foo and invoke method `GetBar()`"

在应用DIP之后,要求被放松,并且关注获得和管理生命周期 Foo 依赖已被删除:

"I need to invoke something which offers `GetBar()`"

为什么要使用DIP(和DI)?

以这种方式解耦类之间的依赖关系允许 容易替代 这些依赖类与其他实现同时满足抽象的先决条件(例如,依赖关系可以用同一接口的另一个实现来切换)。而且,正如其他人所提到的那样  通过DIP解耦类的最常见原因是允许单独测试消耗类,因为这些相同的依赖关系现在可以被存根和/或模拟。

DI的一个结果是依赖对象实例的生命周期管理不再由消费类控制,因为依赖对象现在被传递到消费类(通过构造函数或setter注入)。

这可以通过不同方式查看:

  • 如果需要保留使用类对依赖项的生命周期控制,则可以通过将用于创建依赖项类实例的(抽象)工厂注入到使用者类中来重新建立控制。消费者将能够通过a获取实例 Create 在工厂根据需要,并在完成后处理这些实例。
  • 或者,依赖关系实例的生命周期控制可以放弃到IoC容器(更多关于此内容)。

何时使用DI?

  • 在可能需要将依赖项替换为等效实现的情况下,
  • 在任何时候你需要单独测试类的方法,而不依赖于它的依赖关系,
  • 如果依赖的生命周期的不确定性可能需要实验(例如,嘿, MyDepClass 是线程安全的 - 如果我们将它作为单例并将相同的实例注入所有使用者会怎样?)

这是一个简单的C#实现。鉴于以下消费类:

public class MyLogger
{
   public void LogRecord(string somethingToLog)
   {
      Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
   }
}

虽然看似无害,但它有两个 static 依赖于另外两个类, System.DateTime 和 System.Console,这不仅限制了日志记录输出选项(如果没有人在观察,则记录到控制台将毫无价值),但更糟糕的是,鉴于对非确定性系统时钟的依赖性,很难自动测试。

但是我们可以申请 DIP 通过抽象出时间戳作为依赖关系和耦合的问题来解决这个问题 MyLogger 只有一个简单的界面:

public interface IClock
{
    DateTime Now { get; }
}

我们也可以放松依赖 Console 抽象,例如 TextWriter。依赖注入通常实现为 constructor 注入(将抽象作为参数传递给消费类的构造函数)或者 Setter Injection (通过一个传递依赖 setXyz() setter或.Net属性 {set;} 定义)。构造函数注入是首选,因为这可以保证类在构造之后处于正确的状态,并允许将内部依赖项字段标记为 readonly (C#)或 final (JAVA)。所以在上面的例子中使用构造函数注入,这给我们留下了:

public class MyLogger : ILogger // Others will depend on our logger.
{
    private readonly TextWriter _output;
    private readonly IClock _clock;

    // Dependencies are injected through the constructor
    public MyLogger(TextWriter stream, IClock clock)
    {
        _output = stream;
        _clock = clock;
    }

    public void LogRecord(string somethingToLog)
    {
        // We can now use our dependencies through the abstraction 
        // and without knowledge of the lifespans of the dependencies
        _output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
    }
}

(一个具体的 Clock 需要提供,当然可以恢复 DateTime.Now,这两个依赖项需要由IoC容器通过构造函数注入提供)

可以构建自动化单元测试,这可以明确证明我们的记录器工作正常,因为我们现在可以控制依赖关系 - 时间,我们可以监视书面输出:

[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
    // Arrange
    var mockClock = new Mock<IClock>();
    mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
    var fakeConsole = new StringWriter();

    // Act
    new MyLogger(fakeConsole, mockClock.Object)
        .LogRecord("Foo");

    // Assert
    Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}

下一步

依赖注入总是与a相关联 控制容器(IoC)的反转,注入(提供)具体的依赖实例,并管理生命周期实例。在配置/引导过程中, IoC 容器允许定义以下内容:

  • 每个抽象和配置的具体实现之间的映射(例如, “任何时候消费者要求 IBar,返回一个 ConcreteBar 例如”
  • 可以为每个依赖关系的生命周期管理建立策略,例如,为每个使用者实例创建一个新对象,在所有使用者之间共享一个单独的依赖关系实例,仅在同一个线程上共享同一个依赖关系实例,等等。
  • 在.Net中,IoC容器知道诸如的协议 IDisposable 并将承担责任 Disposing 依赖性与配置的生命周期管理一致。

通常,一旦配置/引导IoC容器,它们就会在后台无缝运行,允许编码器专注于手头的代码,而不是担心依赖性。

DI友好代码的关键是避免类的静态耦合,而不是使用new()来创建依赖项

按照上面的例子,依赖关系的解耦确实需要一些设计工作,对于开发人员来说,需要一种范式转换来打破习惯。 new直接依赖于依赖关系,而是信任容器来管理依赖关系。

但好处很多,特别是在彻底测试你感兴趣的课程的能力。

注意 :创建/映射/投影(通过 new ..())POCO / POJO /序列化DTO /实体图/匿名JSON投影等 - 即“仅数据”类或记录 - 从方法使用或返回  被视为依赖(在UML意义上)并且不受DI的影响。运用 new 投射这些就好了。


34
2018-04-28 20:17



问题是DIP!= DI。 DIP是关于将抽象与实现分离:A。高级模块不应该依赖于低级模块。两者都应该取决于抽象。 B.抽象不应该依赖于细节。细节应取决于抽象。 DI是一种将对象创建与对象使用分离的方法。 - Ricardo Rivaldo
是的,我的第2段明确说明了这一区别, “DI DIP的可能实现之一”,在鲍勃叔叔的SOLID范例中。我也做了这个 明确 在之前的帖子中。 - StuartLC