什么是依赖注入?


3074

已经发布了几个问题,其中包括有关依赖注入的特定问题,例如何时使用它以及使用什么框架。然而,

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


这里查看我对依赖注入的讨论。
Kevin S.

33
我同意有关链接的评论。我了解您可能想推荐其他人。但至少要加上您为什么链接它们,以及使该链接比我使用Google可获得的其他链接更好的原因
Christian Payne 2009年

@AR:从技术上讲,依赖注入不是 IoC的一种特殊形式。相反,IoC是一种用于提供依赖注入的技术。其他技术也可以用来提供依赖注入(尽管IoC是唯一一种常用的技术),IoC也可以用于许多其他问题。
肖恩·赖利

我所读过的有关DI的最好的解释之一是Google的Guice(发音为果汁)http://code.google.com/p/google-guice/wiki/Motivation?tm=6
Raj

136
关于链接,请记住,它们通常会以一种或另一种方式消失。SO答案中的无效链接越来越多。因此,无论链接文章的质量如何,如果找不到它都是不好的。
DOK 2010年

Answers:


1931

依赖注入将依赖传递给其他对象框架(依赖注入器)。

依赖注入使测试更加容易。注入可以通过构造函数完成。

SomeClass() 其构造函数如下:

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

问题:如果myObject涉及复杂的任务,例如磁盘访问或网络访问,则很难对其进行单元测试SomeClass()。程序员必须进行模拟,myObject并可能拦截工厂调用。

替代解决方案

  • myObject作为参数传递给构造函数
public SomeClass (MyClass myObject) {
    this.myObject = myObject;
}

myObject 可以直接通过,这使测试更加容易。

  • 一种常见的替代方法是定义空操作构造函数。依赖注入可以通过setter完成。(h / t @MikeVella)。
  • Martin Fowler记录了第三种选择(h / t @MarcDix),其中类显式实现了程序员希望注入的依赖项的接口

没有依赖注入的情况下,很难在单元测试中隔离组件。

2013年,当我撰写此答案时,这是Google Testing Blog的一个主要主题。这对我来说仍然是最大的优势,因为程序员在运行时设计中并不总是需要额外的灵活性(例如,对于服务定位器或类似的模式)。程序员经常需要在测试期间隔离类。


25
承认本·霍夫斯坦(Ben Hoffstein)对马丁·福勒(Martin Fowler)的文章的引用是指向该主题“必读”的必要条件,我接受wds的回答,因为它实际上回答了SO上的问题。
AR。

121
为解释和动机而+1:创建类依赖于其他人的问题的对象。另一种说法是,DI使类更具凝聚力(它们的职责更少)。
Fuhrmanator 2012年

13
您说依赖项是“传递给构造函数的”,但据我了解,这并非严格如此。如果在实例化对象之后将依赖项设置为属性,那么它仍然是依赖项注入,对吗?
Mike Vella

1
@MikeVella是的,这是正确的。尽管属性通常更灵活,但在大多数情况下它并没有真正的区别。我将稍微编辑文本以指出这一点。
wds 2013年

2
到目前为止,我找到的最好的答案之一,因此我对改进它真的很感兴趣。它缺少对依赖注入的第三种形式的描述:接口注入
马克·迪克斯

2351

到目前为止,我发现的最佳定义是James Shore定义的

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

目前由Martin Fowler的一篇文章,可能证明是有用的,太。

依赖注入基本上是提供对象需要的对象(其依赖),而不是让对象自己构造它们。这是一种非常有用的测试技术,因为它允许对依赖项进行模拟或存根。

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


35
我喜欢詹姆士(James)文章的解释,尤其是结尾部分的内容:“不过,您必须惊叹采用三种概念(“ TripPlanner”,“ CabAgency”和“ AirlineAgency”)的任何方法,并将它们转变成九个以上的类,然后在编写一行应用程序逻辑之前添加数十行粘合代码和配置XML。” 这是我经常看到的(可悲的)-依赖项注入(如他所解释的那样本身就不错)被滥用来使本来可以简化的事情变得过于复杂-最终编写了“支持”代码...
Matt

2
回复:“显式实例化和传递对象(依赖项)与通过框架进行注入一样好。” 那么为什么人们要建立框架来做到这一点呢?
dzieciou 2015年

13
出于同样的原因,每个框架都需要编写(或至少应该编写):因为一旦达到一定的复杂度,就需要编写很多重复/样板代码。问题是很多时候人们即使不需要严格的框架,也会寻求框架。
Thiago Arrais

14
5美分的概念的25美元的合同期限已经结束。这是一篇对我有帮助的好文章:codeproject.com/Articles/615139/…–
Christine

@Matt配置文件仅仅是“推迟决策”,即“推迟决策直到实际运行”。我认为Dagger尤其是Dagger找到了最佳的解决方案“将决定推迟到应用程序组装时间”。
托尔比约恩Ravn的安德森

645

我从松耦合的角度发现了这个有趣的例子:

任何应用程序都由许多相互协作以执行某些有用内容的对象组成。传统上,每个对象都负责获得自己对其与之协作的依赖对象(依赖关系)的引用。这导致了高度耦合的类和难以测试的代码。

例如,考虑一个Car对象。

A Car取决于车轮,发动机,燃料,电池等的运行。传统上,我们会定义此类从属对象的品牌以及对象的定义Car

没有依赖注入(DI):

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

  //The rest
}

在此,Car对象负责创建从属对象。

如果我们想Wheel在初始NepaliRubberWheel()刺孔之后更改其从属对象的类型(例如),该怎么办?我们需要使用其新依赖关系say重新创建Car对象ChineseRubberWheel(),但是只有Car制造商才能做到这一点。

Dependency Injection对我们有什么作用呢?

使用依赖注入时,在运行时而不是编译时(汽车制造时间)为对象提供它们的依赖。这样我们现在就可以随时更改Wheel。在这里,dependencywheel)可以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;
  }
}

资料来源:了解依赖注入


20
我的理解是,我们可以在需要的时候以及是否需要注入该对象,而不是将一个新对象实例化为另一个对象的一部分,从而消除第一个对象对其的依赖。那正确吗?
JeliBeanMachine 2014年

我在这里以咖啡店为例对此进行了描述:digigene.com/design-patterns/dependency-injection-coffeeshop
Ali Nem

11
真的很喜欢这个比喻,因为使用简单的比喻是简单的英语。说我是丰田,已经花了太多的财力和人力上做一个车从设计到滚下组装线,若存在信誉的轮胎生产,我为什么要从头开始做一个轮胎制造部门即new一累?我不。我要做的就是从他们那里购买(通过param注入),安装并哇哇!因此,回到编程,说C#项目需要使用现有的库/类,有两种运行/调试的方法,1-添加对此的整个项目的引用
Jeb50

(续),..外部库/类,或从DLL中添加2个。除非我们必须查看此外部类的内部内容,否则将其添加为DLL是更简单的方法。因此,将选项1 new传递给它,将选项2传递给它作为参数。可能不准确,但简单愚蠢易懂。
Jeb50 '17

1
@JeliBeanMachine(很抱歉,对评论的回复太晚了。。)不是我们删除了第一个对象对wheel对象或battery对象的依赖,而是我们将依赖传递给了它,以便我们可以更改该对象的实例或实现。依赖性。之前:Car对NepaliRubberWheel具有硬编码依赖性。之后:汽车对Wheel实例具有注入依赖性。
Mikael Ohlson '18年

263

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

例如,考虑以下情况:

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::addManagerPersonService::removeManager需要的一个实例GroupMembershipService,以完成其工作。没有依赖注入,传统的方式是GroupMembershipService在构造函数中实例化一个new PersonService并在两个函数中使用该实例属性。但是,如果的构造函数GroupMembershipService需要执行多项操作,或者更糟糕的是,则需要在上调用一些初始化“设置程序”,GroupMembershipService代码会快速增长,PersonService现在不仅取决于GroupMembershipService,还取决于其他所有内容GroupMembershipService取决于。此外,与的链接GroupMembershipService已硬编码到中,PersonService这意味着您无法“虚拟”GroupMembershipService 出于测试目的,或在应用程序的不同部分中使用策略模式。

依赖注入,而不是实例化GroupMembershipService的内PersonService,你要么把它传递到PersonService构造函数,否则添加属性(getter和setter)来设置它的本地实例。这意味着您PersonService不再需要担心如何创建GroupMembershipService,而只需接受给定的,然后与它们一起工作。这也意味着可以将任何作为的子类GroupMembershipService或实现GroupMembershipService接口的东西“注入”到中PersonService,而PersonService无需知道更改。


29
如果您能在以后使用DI给出相同的代码示例
那就太好了

170

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

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

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


18
+1“对象的更改频率比使用它们的代码更频繁”。概括地说,在通量点处添加一个间接。根据流量的不同,这些间接调用的名称也不同!!
Chethan 2014年

139

让我们尝试一个有关CarEngine类的简单示例,至少现在,任何一辆汽车都需要一个可以行驶到任何地方的发动机。因此,下面的代码将在没有依赖项注入的情况下显示。

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

依赖注入的主要好处是,类之间的耦合更为松散,因为它们没有硬编码的依赖关系。这遵循了上面提到的依赖倒置原则。类不引用特定的实现,而是请求构造类时提供给它们的抽象(通常是interface)。

因此,最终依赖注入只是一种用于实现对象及其依赖之间的松散耦合的技术。与其直接实例化类执行其动作所需的依赖关系,不如(通常)通过构造函数注入将依赖关系提供给该类。

同样,当我们有很多依赖项时,最好使用Inversion of Control(IoC)容器,该容器可以告诉我们应该将哪些接口映射到所有依赖项的具体实现上,并且可以在构造时为我们解决这些依赖项我们的对象。例如,我们可以在IoC容器的映射中指定将IEngine依赖项映射到GasEngine类,并且当我们向IoC容器询问Car类的实例时,它将自动构造具有GasEngine依赖项的Car类通过了。

更新:最近观看了朱莉·勒曼(Julie Lerman)的有关EF Core的课程,并且也喜欢她关于DI的简短定义。

依赖注入是一种模式,允许您的应用程序将对象动态注入到需要它们的类中,而不必强制那些类对那些对象负责。它使您的代码可以更松散地耦合在一起,并且Entity Framework Core可以插入该相同的服务系统。


2
只是出于好奇,这与策略模式有何不同?这种模式封装了算法并使它们可互换。感觉依赖注入和策略模式非常相似。
elixir

110

假设您想去钓鱼:

  • 如果没有依赖项注入,则您需要自己做好一切。您需要找到一条船,购买一根钓鱼竿,寻找诱饵等。当然有可能,但是这给您带来了很多责任。用软件术语,这意味着您必须对所有这些内容执行查找。

  • 使用依赖注入,其他人可以完成所有准备工作,并为您提供所需的设备。您将收到(“被注射”)船,钓鱼竿和诱饵-随时可用。


59
缺点是,假设您雇了一个水管工来重做浴室,然后他说:“太好了,这是我需要您拿来的工具和材料清单”。那不是水管工的工作吗?
jscs 2013年

因此,某个人需要照顾一个它不了解业务的人,但是仍然决定收集船,棍棒和鱼饵的清单-尽管可以使用。
Chookoos 2013年

22
@JoshCaswell不,那是水管工的工作。作为客户,您需要完成管道。为此,您需要水管工。水管工需要它的工具。为了得到这些,它由水暖公司装备。作为客户,您不想确切地知道管道工的用途或需求。作为水管工,您知道您需要什么,但您只是想做自己的工作,而不是一无所获。作为管道工的雇主,您有责任在将管道工运送到人们的房屋之前,为他们配备所需的东西。
萨拉

@kai我明白你的意思。在软件方面,我们谈论的是工厂,对吗?但是DI通常也意味着该类不使用工厂,因为仍然没有注入。您(客户)需要联系雇主(工厂)为您提供工具,这样您才能转到水管工那里。这不是它在程序中的实际工作方式吗?因此,尽管客户(打电话给班级/职能部门/无论如何)无需购买工具,但他们仍然必须是中间人,以确保他们从雇主(工厂)那里得到水管工(注塑班)的服务。
KingOfAllTrades

1
@KingOfAllTrades:当然,在某些时候,您必须聘用和配备管道工,或者您没有管道工。但是您没有客户这样做。客户只是要求水管工,然后让一个已经配备了工作所需的水管工。使用DI,您最终仍然会有一些代码来满足依赖关系。但是,您要将其与实际工作的代码分开。如果最大程度地利用它,您的对象只会使它们的依赖关系已知,并且对象图的构建通常在初始化代码中进行。
cHao

102

是我见过的关于依赖注入依赖注入容器的最简单的解释:

没有依赖注入

  • 应用程序需要Foo(例如,控制器),因此:
  • 应用程序创建Foo
  • 应用程序调用Foo
    • Foo需要Bar(例如服务),因此:
    • Foo创建栏
    • Foo电话酒吧
      • Bar需要Bim(服务,存储库等),因此:
      • 栏创建Bim
      • 酒吧做什么

有依赖注入

  • 应用程序需要Foo,需要Bar,需要Bim,因此:
  • 应用程序创建Bim
  • 应用程序创建Bar并将其赋予Bim
  • 应用程序创建Foo并将其赋予Bar
  • 应用程序调用Foo
    • Foo电话酒吧
      • 酒吧做什么

使用依赖注入容器

  • 应用程序需要Foo,因此:
  • 应用程序从容器中获取Foo,因此:
    • 容器创建Bim
    • 容器创建条并将其赋予Bim
    • 容器创建Foo并赋予它Bar
  • 应用程序调用Foo
    • Foo电话酒吧
      • 酒吧做什么

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

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

您不需要容器即可进行依赖项注入。但是,容器可以帮助您。


@rootTraveller google是您的朋友:什么时候不适合使用依赖项注入模式?
Pmpr

55

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

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(); 
    ... 
  } 
}

当然,在DI版本中,您是否不想在no参数构造函数中初始化myDatabase对象?如果您尝试调用DoStuff而不调用重载的构造函数,似乎没有意义,并且将引发异常。
马特·威尔科

仅当new DatabaseThingie()不生成有效的myDatabase实例时。
JaneGoodall

40

使“依赖注入”的概念易于理解。让我们以开关按钮为例,切换(打开/关闭)灯泡。

没有依赖注入

交换机需要事先知道我连接到哪个灯泡(硬编码依赖性)。所以,

开关-> PermanentBulb //开关直接连接到永久灯泡,无法轻松测试

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

有依赖注入

开关只知道我需要打开/关闭任何传递给我的灯泡。所以,

开关-> Bulb1 OR Bulb2 OR NightBulb(注入依赖项)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

修改开关和灯泡的James示例:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

36

什么是依赖注入(DI)?

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

DI,DIP和SOLID

具体而言,在罗伯特·Ç马丁的范式面向对象设计的SOLID原则DI为可能的实现方式之一依赖倒置原则(DIP) 。所述DIP是D所述的SOLID咒语 -其他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注入传递到使用方类中。

可以用不同的方式查看:

  • 如果需要保留使用类对依赖项的生命周期控制,则可以通过将一个用于创建依赖项类实例的(抽象)工厂注入到消费者类中来重新建立控制。消费者将能够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.DateTimeSystem.Console,这不仅限制了记录输出选项(记录到控制台将毫无价值,如果没有人看),但糟糕的是,它是很难自动测试给出的依赖一个不确定的系统时钟。

但是,我们可以DIP通过将时间戳记作为依赖项的关注抽象出来,并MyLogger仅耦合到简单的接口来应用于此类:

public interface IClock
{
    DateTime Now { get; }
}

我们还可以放宽对Console抽象的依赖,例如TextWriter。依赖项注入通常实现为constructor注入(将依赖项的抽象作为消费类的构造函数的参数Setter Injection传递给依赖项)或(通过setXyz()设置器或带有{set;}定义的.Net属性传递依赖项)。最好使用构造函数注入,因为这样可以确保在构造后类将处于正确的状态,并允许将内部依赖项字段标记为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());
}

下一步

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

  • 每个抽象与已配置的具体实现之间的映射(例如“消费者何时请求IBar,返回ConcreteBar实例”
  • 可以为每个依赖项的生命周期管理设置策略,例如为每个使用方实例创建一个新对象,在所有使用方之间共享一个单例依赖项实例,仅在同一线程之间共享同一依赖项实例,等等。
  • 在.Net中,IoC容器了解诸如的协议IDisposable,并将根据Disposing已配置的寿命管理来承担依赖项的责任。

通常,一旦IoC容器已配置/自举,它们将在后台无缝运行,从而使编码人员可以专注于手头的代码,而不必担心依赖关系。

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

如上面的示例所示,依赖关系的分离确实需要一些设计工作,并且对于开发人员而言,需要进行范式转换,以打破new直接使用依赖关系的习惯,而是信任容器来管理依赖关系。

但是好处很多,尤其是能够全面测试您感兴趣的课程。

注意new ..()POCO / POJO /序列化DTO /实体图/匿名JSON投影等的创建/映射/投影(通过)-即“仅数据”类或记录-从方法使用或返回的方法视为依赖项(在UML意义上的),并且不受DI约束。利用new项目,这些仅仅是罚款。


1
问题是DIP!= DI。DIP关于将抽象与实现分离:A.高级模块不应依赖于低级模块。两者都应依赖抽象。B.抽象不应依赖细节。细节应取决于抽象。DI是一种将对象创建与对象使用脱钩的方法。
里卡多·里瓦尔多

是的,在Bob叔叔的SOLID范式中我的第2段“ DI是DIP的可能实现之一”中明确指出了这种区别。我在较早的文章中也明确指出了这一点
StuartLC

25

依赖注入(DI)的全部目的是保持应用程序源代码的清洁稳定

  • 清理依赖初始化代码
  • 稳定,无论是否使用依赖项

实际上,每种设计模式都将关注点分开,以使将来的更改影响最小文件。

DI的特定域是依赖项配置和初始化的委派。

示例:带有shell脚本的DI

如果您偶尔在Java之外工作,请回想一下source许多脚本语言(Shell,Tcl等,甚至import用于此目的的Python)经常使用的语言。

考虑简单的dependent.sh脚本:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

该脚本是依赖的:它不会自行成功执行(archive_files未定义)。

archive_filesarchive_files_zip.sh实现脚本中定义(zip在这种情况下使用):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

而不是source直接在相关脚本中添加实现脚本,而是使用injector.sh“容器”包装两个“组件”:

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files 依赖刚刚被注入相关的脚本。

您可能已经注入了archive_files使用tar或实现的依赖项xz

示例:删除DI

如果dependent.sh脚本直接使用依赖项,则该方法将称为依赖项查找(与依赖项注入相反):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

现在的问题是,依赖的“组件”必须自己执行初始化。

“组件”的源代码既不是干净的也不是稳定的,因为对依赖关系的初始化的每次更改都需要“组件”的源代码文件的新发行版。

最后的话

DI没有像Java框架那样受到广泛的重视和普及。

但这是将关注点分开的通用方法:

  • 应用程序开发单个源代码发布生命周期)
  • 应用程序部署(具有独立生命周期的多个目标环境)

仅通过依赖关系查找使用配置无济于事,因为每个依赖关系(例如,新的身份验证类型)以及支持的依赖关系类型的数量(例如,新的数据库类型)可能会更改配置参数的数量。


我将添加完成特定类(测试)而不必完成其依赖关系的功能,以此作为DI的目的。
大卫

22

以上所有答案都是好的,我的目的是用一种简单的方式解释这个概念,以便没有编程知识的人也可以理解这个概念。

依赖注入是一种设计模式,可以帮助我们以更简单的方式创建复杂的系统。

我们可以在日常生活中看到这种模式的广泛应用。一些示例是录音机,VCD,CD驱动器等。

卷到卷便携式录音机,二十世纪中叶。

上图是20世纪中叶的卷到卷便携式录音机的图像。来源

录音机的主要目的是录制或播放声音。

在设计系统时,需要使用拨盘来录制或播放声音或音乐。设计此系统有两种可能性

  1. 我们可以将卷轴放入机器中
  2. 我们可以提供可放置卷轴的挂钩。

如果使用第一个,则需要打开机器以更换卷轴。如果我们选择第二个,那就是挂在转盘上,那么通过更换转盘,您将获得播放任何音乐的额外好处。并将功能简化为仅播放卷轴中的任何内容。

就像明智的依赖注入一样,是将依赖关系外部化以仅关注组件的特定功能的过程,以便独立的组件可以耦合在一起以形成一个复杂的系统。

我们通过使用依赖项注入获得了主要好处。

  • 高内聚力和松散的耦合。
  • 外部化依赖关系,仅关注责任。
  • 将事物作为组件并进行组合以形成具有高性能的大型系统。
  • 由于它们是独立开发且经过适当测试的,因此有助于开发高质量的组件。
  • 如果一个组件出现故障,则可以用另一个组件替换它。

如今,这些概念构成了编程界众所周知的框架的基础。Spring Angular等是基于此概念构建的著名软件框架

依赖注入是一种用于创建其他对象所依赖的对象实例的模式,这种模式在编译时不知道将使用哪个类来提供该功能或简单地将属性注入对象的方式称为依赖注入。

依赖注入示例

以前我们是这样写的

Public MyClass{
 DependentClass dependentObject
 /*
  At somewhere in our code we need to instantiate 
  the object with new operator  inorder to use it or perform some method.
  */ 
  dependentObject= new DependentClass();
  dependentObject.someMethod();
}

使用依赖注入,依赖注入器将为我们完成实例化

Public MyClass{
 /* Dependency injector will instantiate object*/
 DependentClass dependentObject

 /*
  At somewhere in our code we perform some method. 
  The process of  instantiation will be handled by the dependency injector
 */ 

  dependentObject.someMethod();
}

您也可以阅读

控制反转和依赖注入之间的区别


17

什么是依赖注入?

依赖注入(DI)意味着将相互依赖的对象分离。假设对象A依赖于对象B,所以想法是将这些对象彼此分离。尽管编译时,我们不需要使用new关键字对对象进行硬编码,而在运行时共享对对象的依赖关系。如果我们谈论

Spring依赖注入的工作方式:

我们不需要使用new关键字对对象进行硬编码,而是在配置文件中定义Bean依赖项。弹簧容器将负责所有连接。

控制反转(IOC)

IOC是一个通用概念,可以用许多不同的方式表示,并且依赖注入是IOC的一个具体示例。

两种类型的依赖注入:

  1. 构造函数注入
  2. 二传手注射

1.基于构造函数的依赖注入:

当容器调用带有多个参数的类构造函数时,将完成基于构造函数的DI,每个参数表示对其他类的依赖。

public class Triangle {

private String type;

public String getType(){
    return type;
 }

public Triangle(String type){   //constructor injection
    this.type=type;
 }
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
        <constructor-arg value="20"/>
  </bean>

2.基于Setter的依赖项注入:

通过使用无参数构造函数或无参数静态工厂方法实例化您的bean之后,容器通过在bean上调用setter方法来完成基于setter的DI。

public class Triangle{

 private String type;

 public String getType(){
    return type;
  }
 public void setType(String type){          //setter injection
    this.type = type;
  }
 }

<!-- setter injection -->
 <bean id="triangle" class="com.test.dependencyInjection.Triangle">
        <property name="type" value="equivialteral"/>

注意:将构造函数参数用于强制性依赖项,将设置器用于可选的依赖性,这是一个很好的经验法则。请注意,如果我们在设置器上使用基于注释的@@ Required注释,则可以将其用作必需的依赖项。


15

我能想到的最好的比喻是手术室中的外科医生及其助手,外科医生是主要人物,而他的助手会在需要时提供各种手术部件,以便外科医生可以专注于一个他最擅长的事情(手术)。没有助手,外科医生就必须在每次需要时自己准备零件。

简而言之,DI是一种通过向组件提供依赖来消除组件上常见的附加责任(负担)以提取依赖组件的技术。

DI使您更接近单一责任(SR)原则,例如surgeon who can concentrate on surgery

何时使用DI:我建议几乎在所有生产项目(小型/大型)中都使用DI,尤其是在瞬息万变的商业环境中:)

原因:因为您希望代码易于测试,可模拟等,以便您可以快速测试更改并将其推向市场。此外,为什么当您有很多很棒的免费工具/框架可以为您提供更多控制权的代码库之旅中提供支持时,您为什么不这样做呢?


@WindRider谢谢。我完全同意。人的生命和人体是卓越设计的绝佳例证。脊柱是ESB的绝佳例证:)...
Anwar Husain

15

例如,我们有2个类ClientServiceClient将使用Service

public class Service {
    public void doSomeThingInService() {
        // ...
    }
}

没有依赖注入

方式1)

public class Client {
    public void doSomeThingInClient() {
        Service service = new Service();
        service.doSomeThingInService();
    }
}

方式2)

public class Client {
    Service service = new Service();
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

方式3)

public class Client {
    Service service;
    public Client() {
        service = new Service();
    }
    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

1)2)3)使用

Client client = new Client();
client.doSomeThingInService();

优点

  • 简单

缺点

  • 很难Client上课
  • 更改Service构造函数时,需要在所有位置更改代码以创建Service对象

使用依赖注入

方式1)构造器注入

public class Client {
    Service service;

    Client(Service service) {
        this.service = service;
    }

    // Example Client has 2 dependency 
    // Client(Service service, IDatabas database) {
    //    this.service = service;
    //    this.database = database;
    // }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用

Client client = new Client(new Service());
// Client client = new Client(new Service(), new SqliteDatabase());
client.doSomeThingInClient();

方式2) Setter注入

public class Client {
    Service service;

    public void setService(Service service) {
        this.service = service;
    }

    public void doSomeThingInClient() {
        service.doSomeThingInService();
    }
}

使用

Client client = new Client();
client.setService(new Service());
client.doSomeThingInClient();

方式3)接口注入

检查https://en.wikipedia.org/wiki/Dependency_injection

===

现在,此代码已经在后面Dependency Injection,对于测试Client类来说更容易。
但是,我们仍然会new Service()花费很多时间,并且在更改Service构造函数时效果不好。为了防止这种情况,我们可以使用DI注射器,例如
1)简单手册Injector

public class Injector {
    public static Service provideService(){
        return new Service();
    }

    public static IDatabase provideDatatBase(){
        return new SqliteDatabase();
    }
    public static ObjectA provideObjectA(){
        return new ObjectA(provideService(...));
    }
}

使用

Service service = Injector.provideService();

2)使用库:适用于Android dagger2

优点

  • 使测试更轻松
  • 更改时Service,只需在Injector类中进行更改
  • 如果使用use Constructor Injection,则在查看的构造函数时Client,您会看到Client该类有多少依赖关系

缺点

  • 如果使用use Constructor InjectionService则在Client创建对象时创建该对象,有时我们在Client类中使用函数而不使用它,Service因此Service浪费了创建的时间

依赖注入定义

https://zh.wikipedia.org/wiki/Dependency_injection

依赖项是可以使用的对象(Service
注入是将依赖项(Service)传递给Client将使用它的依赖对象()


13

这意味着对象应该只具有完成其工作所需的依赖关系,而依赖关系应该很少。此外,在可能的情况下,对象的依赖项应位于接口上,而不应位于“具体”对象上。(具体对象是使用关键字new创建的任何对象。)松散耦合可提高重用性,更易于维护,并允许您轻松提供“模拟”对象来代替昂贵的服务。

“依赖注入”(DI)也称为“控制反转”(IoC),可以用作鼓励这种松散耦合的技术。

实施DI的主要方法有两种:

  1. 构造器注入
  2. 二传手注射

构造器注入

这是将对象依赖项传递给其构造函数的技术。

请注意,构造函数接受接口,而不接受具体对象。另外,请注意,如果orderDao参数为null,则会引发异常。这强调了接收有效依赖关系的重要性。在我看来,构造函数注入是赋予对象依赖项的首选机制。开发人员很清楚在调用对象时需要将哪些依赖项赋予“ Person”对象以正确执行。

二传手注射

但是,请考虑以下示例……假设您有一个类,其中包含十个没有依赖关系的方法,但是您要添加一个确实依赖于IDAO的新方法。您可以将构造函数更改为使用“构造函数注入”,但这可能会迫使您更改所有位置的所有构造函数调用。另外,您可以只添加一个带有依赖项的新构造函数,但是开发人员如何轻松知道何时使用一个构造函数而不是另一个。最后,如果创建的依赖项非常昂贵,那么当它很少使用时,为什么还要创建它并将其传递给构造函数呢?“注浆注入”是另一种可以在这种情况下使用的DI技术。

Setter Injection不会强制将依赖项传递给构造函数。而是将依赖项设置为需要的对象公开的公共属性。如前所述,这样做的主要动机包括:

  1. 支持依赖注入,而不必修改旧类的构造函数。
  2. 允许尽可能晚且仅在需要时才创建昂贵的资源或服务。

以下是上述代码的示例:

public class Person {
    public Person() {}

    public IDAO Address {
        set { addressdao = value; }
        get {
            if (addressdao == null)
              throw new MemberAccessException("addressdao" +
                             " has not been initialized");
            return addressdao;
        }
    }

    public Address GetAddress() {
       // ... code that uses the addressdao object
       // to fetch address details from the datasource ...
    }

    // Should not be called directly;
    // use the public property instead
    private IDAO addressdao;

3
我认为您的第一段偏离了这个问题,根本不是DI的定义(即,您正在尝试定义SOLID,而不是DI)。从技术上讲,即使您有100个依赖项,仍然可以使用依赖项注入。同样,可以注入具体的依赖关系,它仍然是依赖关系注入。
杰伊·沙利文

10

我想既然每个人都为DI写作,所以让我问几个问题。

  1. 当您有一个DI配置时,所有实际实现(不是接口)都将注入到一个类中(例如,给控制器的服务),那为什么不是某种硬编码呢?
  2. 如果我想在运行时更改对象怎么办?例如,我的配置已经说明了在实例化MyController时,将FileLogger注入为ILogger。但是我可能想注入DatabaseLogger。
  3. 每当我想更改AClass所需的对象时,现在都需要检查两个地方-类本身和配置文件。如何使生活更轻松?
  4. 如果未注入AClass的Aproperty,是否更难模拟它?
  5. 回到第一个问题。如果使用new object()不好,我们为什么要注入实现而不是接口?我想很多人都说我们实际上是在注入接口,但是配置使您可以指定该接口的实现..而不是在运行时..在编译时会对其进行硬编码。

这基于@Adam N发布的答案。

为什么PersonService不再需要担心GroupMembershipService?您刚刚提到GroupMembership有多个依赖项(对象/属性)。如果PService中需要GMService,则将其作为属性。无论是否注入,都可以模拟出来。我唯一希望注入的是GMService是否具有更特定的子类,直到运行时您才知道。然后,您想注入子类。或者,如果您想将其用作单例或原型。坦白说,配置文件对所有内容进行了硬编码,直到它在编译期间将要注入的类型(接口)的子类为止。

编辑

Jose Maria Arranz在DI上发表了不错的评论

DI通过消除确定依赖性方向和编写任何胶合代码的任何需求来增加内聚力。

假。依赖关系的方向是XML形式或作为注释,您的依赖关系被编写为XML代码和注释。XML和注释是源代码。

DI通过使您的所有组件模块化(即可更换)并具有相互定义的接口来减少耦合。

假。您不需要DI框架即可基于接口构建模块化代码。

关于可替换:使用非常简单的.properties存档和Class.forName,您可以定义可以更改的类。如果可以更改代码的任何类,则Java不适合您,请使用脚本语言。顺便说一句:注释必须重新编译才能更改。

在我看来,DI框架的唯一原因是:减少锅炉板。有了完善的工厂系统,您可以像首选的DI框架一样执行相同的操作,并且可控性强,可预测性强,DI框架可以减少代码(XML和注释也是源代码)。问题在于,在非常简单的情况下(每个类一个实例,以及类似的实例),减少样板只是真实的,有时在现实世界中,选择合适的服务对象并不像将类映射到单例对象那样容易。


8

流行的答案无济于事,因为它们以一种无用的方式定义了依赖注入。让我们同意,“依赖”是指对象X需要的一些预先存在的其他对象。但是当我们说时,我们并不是说我们在做“依赖注入”

$foo = Foo->new($bar);

我们只是将传递参数称为构造函数。自从构造函数被发明以来,我们就一直定期这样做。

“依赖注入”被认为是“控制反转”的一种,这意味着某些逻辑会从调用者中取出。调用者传递参数时不是这种情况,因此,如果参数是DI,则DI不会暗示控制权的倒置。

DI表示在调用方和构造程序之间存在一个中间层,用于管理依赖项。Makefile是依赖项注入的一个简单示例。“调用者”是在命令行上键入“ make bar”的人,而“构造函数”是编译器。Makefile指定bar取决于foo,并且它执行

gcc -c foo.cpp; gcc -c bar.cpp

在做一个之前

gcc foo.o bar.o -o bar

输入“ make bar”的人不需要知道bar取决于foo。依赖项被注入“ make bar”和gcc之间。

中间级别的主要目的不仅是将依赖项传递给构造函数,而且还列出了所有依赖项。 一个位置,并从编码器中隐藏它们(而不是让编码器提供它们)。

通常,中间级别为构造的对象提供工厂,工厂必须提供每个请求的对象类型必须满足的角色。那是因为通过具有隐藏构造细节的中间层,您已经招致了工厂施加的抽象损失,因此您最好使用工厂。


8

依赖注入是指一种方式(实际上是任意方式),使一部分代码(例如,一个类)可以以模块化的方式访问依赖(例如,代码的其他部分,例如,其他类,它依赖),而无需对其进行硬编码(因此它们可以根据需要自由更改或覆盖,甚至可以在其他时间加载)

(和ps,是的,因为一个非常简单的概念它已被过度炒作了25美元),我的.25美分


8

我知道已经有很多答案了,但是我发现这很有帮助:http : //tutorials.jenkov.com/dependency-injection/index.html

无依赖关系:

public class MyDao {

  protected DataSource dataSource = new DataSourceImpl(
    "driver", "url", "user", "password");

  //data access methods...
  public Person readPerson(int primaryKey) {...}     
}

依赖关系:

public class MyDao {

  protected DataSource dataSource = null;

  public MyDao(String driver, String url, String user, String password) {
    this.dataSource = new DataSourceImpl(driver, url, user, password);
  }

  //data access methods...
  public Person readPerson(int primaryKey) {...}
}

请注意,DataSourceImpl实例化如何移动到构造函数中。构造函数采用四个参数,它们是所需的四个值DataSourceImpl。尽管MyDao该类仍依赖于这四个值,但它本身已不再满足这些依赖关系。它们由创建MyDao实例的任何类提供。


1
DI不会通过已构造的DataSourceImp通过接口传递您吗?
PmanAce

6

依赖注入是一种解决方案,它通常可以称为“依赖关系混淆”要求。依赖混淆是一种从“明显”性质中消除对需要它的类提供依赖的过程的方法,因此以某种方式混淆了对所述类的所述依赖的提供。这不一定是一件坏事。实际上,通过混淆向类提供依赖项的方式,然后类外部的某些事物将负责创建依赖项,这意味着在各种情况下,可以将依赖项的不同实现提供给类,而无需进行任何更改上课。这非常适合在生产和测试模式之间进行切换(例如,使用“模拟”服务依赖项)。

不幸的是,不幸的是,有些人认为您需要一个专门的框架来进行依赖关系模糊处理,并且如果您选择不使用特定的框架来进行编程,那么您在某种程度上就是一个“较少”的程序员。许多人认为,另一个令人极为不安的神话是,依赖注入是实现依赖混淆的唯一方法。从历史上可以证明,这显然是100%错误的,但是您将难以令人信服某些人,对于依赖项混淆要求,可以使用依赖项注入替代方法。

程序员已经了解依赖混淆的要求已有多年了,并且在构思依赖注入之前和之后,许多替代解决方案都在不断发展。有Factory模式,但也有许多使用ThreadLocal的选项,其中不需要注入到特定实例-依赖关系被有效地注入到线程中,这具有使对象可用(通过便捷的静态getter方法)可用的好处。任何需要它的类,而不必在需要它的类中添加注释,并设置复杂的XML“胶水”来实现它。当您的依赖关系是持久性所必需的(JPA / JDO或其他)时,它使您可以更轻松地实现“透明持久性”,并且领域模型和业务模型类完全由POJO组成(即,没有特定于框架/未锁定在注释中)。



5

在进行技术描述之前,首先用一个真实的例子将其可视化,因为您会发现很多技术知识来学习依赖关系注入,但是像我这样的人最多只能了解它的核心概念。

在第一张图片中,假设您有一家团结很多的汽车厂。汽车实际上是安装在装配单元中的,但是它需要发动机座椅车轮。因此,组装单元取决于所有这些单元,它们是工厂的依赖

您可能会觉得现在很难在这个工厂中维护所有任务,因为除了主要任务(在组装单元中组装汽车)之外,您还必须专注于其他单元。现在它的维护成本非常高,工厂厂房巨大,因此您需要多付一些租金。

现在,看第二张图片。如果您找到一些供应商公司,这些公司可以为您提供比您自己生产的价格便宜的车轮座椅发动机,那么您现在不需要在工厂生产它们。您现在可以租一间较小的建筑物,仅用于组装单位这将减少维护工作并减少额外的租赁成本。现在,您也可以只专注于主要任务(汽车装配)。

现在我们可以说,组装汽车的所有依赖关系都是从提供商那里注入的。这是现实生活中的依赖注入(DI)的一个示例

现在用专业术语来说,依赖注入是一种技术,通过这种技术,一个对象(或静态方法)提供了另一个对象的依赖关系。因此,将创建对象的任务转移给其他人并直接使用依赖项的过程称为依赖项注入。

现在,将帮助您使用一些技巧性单词来学习DI。将显示,当使用DI时候应该不会

多合一汽车厂

简易车工厂


1
40道菜中最清晰的答案。现实生活中的例子和图像。+1。应该是公认的答案。
马尔凯雷米

4

摘自Book Apress.Spring.Persistence.Hibernate.Oct.2010

依赖项注入的目的是将解析外部软件组件的工作与应用程序业务逻辑分离开来。如果没有依赖项注入,组件代码如何访问组件访问所需服务的详细信息就会变得混乱。这不仅增加了潜在的错误,增加了代码膨胀,而且增加了维护复杂性;它将组件更紧密地耦合在一起,从而在重构或测试时很难修改依赖关系。


4

依赖注入(DI)是设计模式中的一种,它使用OOP的基本功能-一个对象与另一个对象之间的关系。继承继承了一个对象以执行更复杂和特定的另一个对象时,关系或关联只是使用属性从一个对象创建指向另一个对象的指针。DI的功能与接口和隐藏代码一样,与OOP的其他功能结合在一起。假设我们在图书馆中有一个客户(订户),为简单起见,该客户只能借用一本书。

书的界面:

package com.deepam.hidden;

public interface BookInterface {

public BookInterface setHeight(int height);
public BookInterface setPages(int pages);   
public int getHeight();
public int getPages();  

public String toString();
}

接下来,我们可以有很多书籍。一种类型是小说:

package com.deepam.hidden;

public class FictionBook implements BookInterface {
int height = 0; // height in cm
int pages = 0; // number of pages

/** constructor */
public FictionBook() {
    // TODO Auto-generated constructor stub
}

@Override
public FictionBook setHeight(int height) {
  this.height = height;
  return this;
}

@Override
public FictionBook setPages(int pages) {
  this.pages = pages;
  return this;      
}

@Override
public int getHeight() {
    // TODO Auto-generated method stub
    return height;
}

@Override
public int getPages() {
    // TODO Auto-generated method stub
    return pages;
}

@Override
public String toString(){
    return ("height: " + height + ", " + "pages: " + pages);
}
}

现在,订户可以关联到该书:

package com.deepam.hidden;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Subscriber {
BookInterface book;

/** constructor*/
public Subscriber() {
    // TODO Auto-generated constructor stub
}

// injection I
public void setBook(BookInterface book) {
    this.book = book;
}

// injection II
public BookInterface setBook(String bookName) {
    try {
        Class<?> cl = Class.forName(bookName);
        Constructor<?> constructor = cl.getConstructor(); // use it for parameters in constructor
        BookInterface book = (BookInterface) constructor.newInstance();
        //book = (BookInterface) Class.forName(bookName).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
    return book;
}

public BookInterface getBook() {
  return book;
}

public static void main(String[] args) {

}

}

这三个类都可以隐藏,因为它是自己实现的。现在我们可以将此代码用于DI了:

package com.deepam.implement;

import com.deepam.hidden.Subscriber;
import com.deepam.hidden.FictionBook;

public class CallHiddenImplBook {

public CallHiddenImplBook() {
    // TODO Auto-generated constructor stub
}

public void doIt() {
    Subscriber ab = new Subscriber();

    // injection I
    FictionBook bookI = new FictionBook();
    bookI.setHeight(30); // cm
    bookI.setPages(250);
    ab.setBook(bookI); // inject
    System.out.println("injection I " + ab.getBook().toString());

    // injection II
    FictionBook bookII = ((FictionBook) ab.setBook("com.deepam.hidden.FictionBook")).setHeight(5).setPages(108); // inject and set
    System.out.println("injection II " + ab.getBook().toString());      
}

public static void main(String[] args) {
    CallHiddenImplBook kh = new CallHiddenImplBook();
    kh.doIt();
}
}

有许多不同的方法来使用依赖项注入。可以将它与Singleton等结合使用,但是从根本上讲,它仅是通过在另一个对象内部创建对象类型的属性来实现的关联。有用性仅在功能方面,我们应该一遍又一遍地编写的代码始终是为我们准备和完成的。这就是为什么DI与控制反转(IoC)如此紧密地绑定的原因,这意味着我们的程序将控制另一个正在运行的模块,该模块将Bean注入到我们的代码中。(每个可以注入的对象都可以签名或视为Bean。)例如,在Spring中,它是通过创建和初始化来完成的 ApplicationContext容器,为我们完成这项工作。我们只需在代码中创建Context并调用初始化bean。在那一刻,注射已自动完成。


4

5岁儿童的依赖注射。

当您自己将物品从冰箱中取出时,可能会引起问题。您可能会打开门,可能会得到妈妈或爸爸不想要的东西。您甚至可能在寻找我们什至没有或已经过期的东西。

您应该做的是陈述一个需求,“我需要在午餐时喝点东西”,然后当您坐下来吃饭时,我们将确保您有东西。


1
这显然是父母的答案。;)
马尔凯·雷米

4

从Pablo Deeleman的书Christopher Noring的“ Learning Angular-Second Edition”:

“随着我们的应用发展和演变,我们的每一个代码实体将在内部需要其他对象的实例,这是更好地称为依赖该软件工程的世界行动通过这样的依赖于相关的客户端被称为注射,而且还需要另一个代码实体命名的参与喷油器,该喷油器将承担责任实例化自举所需的依赖因此,从成功将其注入客户端以来,它们就可以使用了。这是非常重要的,因为客户端对如何实例化自己的依赖关系一无所知,并且仅知道它们为使用它们而实现的接口。”

来自:安东·莫伊谢夫(Anton Moiseev)。本书“使用Typescript进行角度开发,第二版”:

“简而言之,DI帮助您以松散耦合的方式编写代码,并使您的代码更具可测试性可重用性。”


3

简而言之,依赖注入(DI)是消除不同对象之间的依赖或紧密耦合的方法。依赖注入为每个对象赋予了凝聚力。

DI是Spring的IOC负责人的实现,说“不要打电话给我们,我们会打电话给您”。使用依赖注入,程序员不需要使用new关键字创建对象。

对象一旦被加载到Spring容器中,然后就可以通过使用getBean(String beanName)方法从Spring容器中获取这些对象来重用它们。


3

依赖注入是与Spring Framework相关的概念的核心。尽管创建任何项目spring的框架都可能起着至关重要的作用,但在这里,依赖注入成为了投手。

实际上,假设在Java中创建了两个不同的类,分别为A类和B类,并且想要在B类中使用的功能都想在A类中使用,那么那时可以使用依赖注入。您可以在其中创建另一个类的对象的方式相同,也可以将整个类注入另一个类以使其可访问。通过这种方式,可以克服依赖性。

依赖注射仅是将两个类别粘合在一起,并同时将它们分开。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.