控制反转与依赖注入

709
根据Martin Fowler撰写的论文,控制反转是一种程序控制流逆转的原则: 程序员不再控制程序的流程,而是由外部来源(框架、服务、其他组件)接管控制。就像我们把某物插入另一个物品中。他举了EJB 2.0的例子:

例如Session Bean接口定义了ejbRemove,ejbPassivate(存储到辅助存储器)和ejbActivate(从被动状态恢复),您无法控制何时调用这些方法,只能控制它们执行的操作。容器会调用我们,我们不会调用它。

这导致了框架和库之间的差异:

控制反转是使框架与库不同的关键部分。库本质上是一组函数,您可以调用这些函数,现在通常组织成类。每个调用都会执行一些工作,然后返回控制权给客户端。

我认为,DI是IOC的观点意味着对象的依赖性被倒置:它不再控制自己的依赖关系、生命周期等,而是由其他东西为您完成。但是,正如您告诉我的那样,手动进行DI并不一定是IOC。我们仍然可以进行DI而没有IOC。

然而,在这篇文章中(来自另一个面向C/C++的IOC框架pococapsule),它建议由于IOC和DI,IOC容器和DI框架比J2EE更加优越,因为J2EE将框架代码混合到组件中,因此不能使其成为纯旧的Java/C++对象(POJO/POCO)。

除了依赖注入模式之外的控制反转容器(存档链接)

额外阅读以理解旧的基于组件的开发框架存在的问题,从而导致上述第二篇论文:控制反转的原因和内容(存档链接)

我的问题:什么是IOC和DI?我很困惑。根据pococapsule的说法,IOC不仅仅是对象或程序员与框架之间的控制反转。


5
这里有一篇关于IoC vs DI(依赖注入)vs SL(服务定位器)的好文章:http://tinyurl.com/kk4be58 - 从URL中提取:IoC vs DI(依赖注入)?IoC是一个通用概念,其中流程控制从客户端代码反转到框架,“为客户端执行某些操作”。SL(服务定位器)和DI(依赖注入)是从IoC衍生出来的两种设计模式。 - Swab.Jat
顺便说一句,如果有人对依赖注入在咖啡店主题中如何有帮助感兴趣,我在这里写了一篇文章:digigene.com/design-patterns/dependency-injection-coffeeshop - Ali Nem
3
初学者可读的不错文章:https://asimplify.com/dependency-injection-inversion-control/该文章介绍了依赖注入和控制反转的概念,使用通俗易懂的语言解释了它们在软件开发中的作用和意义。阅读本文可以让初学者更好地理解这两个重要的概念,并为之后的学习打下基础。 - Khawaja Asim
依赖倒置:依赖于抽象而不是具体实现。控制反转:主程序与抽象之间的关系,以及主程序是系统的粘合剂。以下是一些讨论此问题的好文章:https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-avoid-it/ https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/ https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/ - Daniel Andres Pelaez Lopez
1
阅读这篇深入的文章,它将澄清一切 https://martinfowler.com/articles/dipInTheWild.html#YouMeanDependencyInversionRight - Dusman
24个回答

6

控制反转是一种通用的软件架构设计原则,有助于创建可重用、模块化的软件框架,易于维护。

它是一种设计原则,其中控制流从通用编写的库或可重用代码“接收”。

为了更好地理解它,让我们看看我们在编程早期如何编写代码。在过程式/传统语言中,业务逻辑通常控制应用程序的流程,并“调用”通用或可重用的代码/函数。例如,在简单的控制台应用程序中,我的控制流由程序指令控制,可能包括对一些通用可重用函数的调用。

print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);

//More print and scan statements
<Do Something Interesting>

//Call a Library function to find the age (common code)
print Age

相比之下,使用控制反转(IoC)时,框架是可重用的代码,用于“调用”业务逻辑。
例如,在基于Windows的系统中,已经有一个框架可用于创建诸如按钮、菜单、窗口和对话框等UI元素。当我编写应用程序的业务逻辑时,框架的事件将调用我的业务逻辑代码(在触发事件时),而不是相反。
虽然框架的代码不知道我的业务逻辑,但它仍然知道如何调用我的代码。这是通过事件/委托、回调等实现的。在这里,控制流程被“倒置”。
因此,控制流不再依赖于静态绑定的对象,而是依赖于整个对象图和不同对象之间的关系。
依赖注入是一种实现对象依赖项的IoC原则的设计模式。
简单地说,当您尝试编写代码时,您将创建和使用不同的类。一个类(Class A)可能会使用其他类(Class B和/或D)。因此,类B和D是类A的依赖项。
一个简单的类比可以是一个汽车类。汽车可能依赖于其他类,如发动机、轮胎等。
依赖注入建议,应该将类注入其依赖关系的具体实例,而不是依赖类(例如汽车类)创建其依赖项(例如引擎类和轮胎类)。
让我们通过一个更实际的例子来理解。假设您正在编写自己的文本编辑器。除其他功能外,您可以拥有一个拼写检查器,为用户提供检查其文本中拼写错误的工具。这样一个简单的代码实现可以是:
Class TextEditor
{

    //Lot of rocket science to create the Editor goes here

    EnglishSpellChecker objSpellCheck;
    String text;

    public void TextEditor()

    {   

        objSpellCheck = new EnglishSpellChecker();

    }

    public ArrayList <typos> CheckSpellings()
    {

        //return Typos;

    }

}

乍一看,一切都看起来很顺利。用户将写入一些文本。开发人员将捕获该文本并调用CheckSpellings函数,然后找到一系列拼写错误,并将其显示给用户。
一切似乎都很好,直到有一天有一个用户开始在编辑器中使用法语。
为了支持更多的语言,我们需要有更多的拼写检查器。可能是法语、德语、西班牙语等。
在这里,我们创建了一个紧密耦合的代码,“English”SpellChecker与我们的TextEditor类紧密耦合,这意味着我们的TextEditor类依赖于EnglishSpellChecker或者换句话说,EnglishSpellChecker是TextEditor的依赖项。我们需要消除这种依赖关系。此外,我们的文本编辑器需要一种方式,在运行时根据开发人员的决定来保留任何拼写检查程序的具体引用。
因此,正如我们在DI的介绍中所看到的那样,它建议应向类注入其依赖项。因此,将所有依赖项注入到所调用的类/代码中应由调用代码负责。因此,我们可以重组我们的代码。
interface ISpellChecker
{

    Arraylist<typos> CheckSpelling(string Text);

}

Class EnglishSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}



Class FrenchSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}

在我们的例子中,TextEditor类应该接收ISpellChecker类型的具体实例。
现在,这个依赖可以通过构造函数、公共属性或方法注入。
让我们尝试使用构造函数依赖注入来更改我们的类。修改后的TextEditor类将如下所示:
Class TextEditor

{

    ISpellChecker objSpellChecker;

    string Text;



    public void TextEditor(ISpellChecker objSC)

    {

        objSpellChecker = objSC;

    }



    public ArrayList <typos> CheckSpellings()

    {

        return objSpellChecker.CheckSpelling();

    }

}

这样,当调用代码创建文本编辑器时,可以将适当的SpellChecker类型注入到TextEditor实例中。

您可以在此处阅读完整文章。


6

DIIOC是两种主要关注提供组件之间松耦合的设计模式,或者简单来说,它们是解除对象之间传统依赖关系的一种方式,以使对象之间不紧密相连。

通过以下示例,我试图解释这两个概念。

以前我们编写的代码是这样的:

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

上述过程将控制权交给其他人(例如容器)进行实例化和注入,可以称为控制反转,IOC容器为我们注入依赖项的过程可以称为依赖注入。
IOC是程序控制流程倒置的原则:程序控制流程而不是程序员控制流程,通过减少程序员的负担来控制流程。程序用于注入依赖项的过程称为DI。
这两个概念共同作用,为我们提供了一种编写更加灵活、可重用、封装的代码的方法,使它们成为设计面向对象解决方案的重要概念。
也推荐阅读。 什么是依赖注入? 您也可以在此处检查我的一个类似答案 控制反转和依赖注入之间的区别

5

IOC(控制反转): 将对象的实例化控制权交给容器,这就是控制反转。这意味着,与其使用new操作符创建对象,让容器为您执行此操作。

DI(依赖注入): 从 XML 将所需参数(属性)传递到对象(在 POJO 类中)中,这称为依赖注入。


5

该链接不可用。 - Palindromer

4
IOC表示外部类管理应用程序的类,外部类意味着容器管理应用程序类之间的依赖关系。IOC的基本概念是程序员不需要创建对象,而是描述应如何创建它们。
IoC容器执行的主要任务包括:实例化应用程序类、配置对象以及组装对象之间的依赖关系。
DI是在运行时使用setter注入或构造函数注入来提供对象依赖关系的过程。

4

IOC(控制反转)基本上是一种设计模式的概念,旨在消除依赖关系并将其解耦,使流程变得非线性,并让容器/或另一个实体管理依赖关系的提供。它实际上遵循好莱坞原则“不要打电话给我们,我们会打电话给你”。

总结差异:

控制反转:它是一种通用术语,用于解耦依赖关系并委派它们的提供,可以通过事件、委托等多种方式实现。

依赖注入:DI 是 IOC 的子类型,通过构造函数注入、setter 注入或方法注入来实现。

以下文章非常清晰地描述了这一点。

https://www.codeproject.com/Articles/592372/Dependency-Injection-DI-vs-Inversion-of-Control-IO


3

我认为可以清楚地展示这个想法,而不必深入涉及面向对象编程的细节,这些似乎会使想法变得混乱。

// dependency injection
function doSomething(dependency) {
    // do something with your dependency
}

// in contrast to creating your dependencies yourself
function doSomething() {
    dependency = getDependencySomehow()
}

// inversion of control
application = makeApp(authenticate, handleRequest, sendResponse)
application.run(getRequest())

// in contrast to direct control or a "library" style
application = makeApp()
request = application.getRequest()

if (application.authenticate(request.creds)) {
    response = application.handleRequest(request)
    application.sendResponse(response)
}

如果你歪着头眯起眼睛看,就会发现DI是一种特定的IoC实现,具有特定的关注点。你不是将模型和行为注入到应用程序框架或高阶操作中,而是将变量注入到函数或对象中。

1

DIP vs DI vs IoC

[依赖倒置原则(DIP)][关于]SOLID原则的一部分,它要求您使用抽象而不是实现。

依赖注入(DI) - 使用聚合而不是组合[关于]。在这种情况下,外部对象负责内部逻辑。这使您可以拥有更动态和可测试的方法。

class A {
  B b

  //injecting B via constructor 
  init(b: B) {
     self.b = b
  }
}

控制反转(IoC)是一个非常高层次的定义,更多关注于控制流。最好的例子是控制反转(IoC)容器或框架[关于]。例如GUI是一个框架,你没有控制权,你可以做的就是实现框架的接口,当框架中发生某些操作时,接口将被调用。因此,控制权从你的应用程序转移到了使用的框架中。

DIP + DI

class A {
  IB ib

  init(ib: IB) {
     self.ib = ib
  }
}

您还可以使用以下方法实现:

更复杂的示例

多层/模块结构中的依赖规则。

伪代码:

interface InterfaceInputPort {
    func input()
}

interface InterfaceOutputPort {
    func output()
}

class A: InterfaceOutputPort {

    let inputPort = B(outputPort: self)

    func output() {
        print("output")
    }
}

class B: InterfaceInputPort {
    let outputPort: InterfaceOutputPort

    init(outputPort: InterfaceOutputPort) {
        self.outputPort = outputPort
    }

    func input() {
        print("input")
    }
}


1

让我们从SOLID的D开始,看一下Scott Millett的书《Professional ASP.NET设计模式》中的DI和IoC:

依赖倒置原则(DIP) DIP 是关于将类与具体实现隔离并使其依赖于抽象类或接口的理念。它提倡通过编程接口而不是实现来增加系统的灵活性,从而确保您不会与一个实现紧密耦合。 依赖注入(DI)和控制反转(IoC) 与 DIP 密切相关的是 DI 原则和 IoC 原则。 DI 是通过构造函数、方法或属性提供低级别或依赖类的行为。与 DI 结合使用,这些依赖类可以被反转为接口或抽象类,从而导致松散耦合的系统,这些系统易于测试和更改。
IoC 中,系统的控制流与过程式编程相比被反转了。其中一个例子是 IoC 容器,其目的是在没有客户端代码指定具体实现的情况下向客户端代码注入服务。在这种情况下被反转的控制是获取服务的行为。
Millett,C (2010). 专业ASP.NET设计模式. Wiley Publishing. 7-8.

1

IoC的概念最初出现在过程式编程时代。因此,从历史背景来看,IoC谈论的是控制流的反转,即谁拥有按照所需顺序调用函数的责任——是函数本身还是应该将其反转到某个外部实体。

然而,一旦面向对象编程出现,人们开始在面向对象编程的上下文中谈论IoC,这里的应用程序涉及对象创建及其关系,除了控制流之外。这些应用程序希望反转对象创建的所有权(而不是控制流),并需要一个容器负责对象的创建、对象生命周期和注入应用程序对象的依赖项,从而消除应用程序对象创建其他具体对象的情况。

从这个意义上讲,DI与IoC不同,因为它不涉及控制流,但它是一种Io*,即对象创建所有权的反转。

我解释DI和IoC的方式有什么问题?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接