在理解依赖注入之前,必须先理解其中的依赖是什么。对于我们开发的程序而言,实际上就是通过不同类型的对象相互协作而构建成的应用,例如在订单类中,就会引用商品类作为某个属性。由于类于类之间存在这种引用关系,在类中就避免不了通过“new”对引用的外部类型进行实例化,对于这种现象就会促使应用程序代码中产生依赖。
对于应用程序代码中存在的这种“依赖关系”,其实通过一种“机械齿轮图”就可以很直观的体会到这种依赖关系所带来的弊端。每个类就像某个齿轮,齿轮之间的啮合传动就像类于类之间的依赖关系。通过“机械齿轮图”的运作场景,我们不难看出这种结构的一种弊端:就是不同的齿轮会相互影响,如果有一个齿轮出了问题,就可能会影响到整个齿轮组的正常运转,对于这种现象,放到应用程序中也是无法避免的现实。

当然,这种依赖对于应用程序带来的后果并不是很直观,通常在.NET Framework时代可能很多项目都存在这种依赖现象,但这种现象并不是一个良好的设计规范,并且会带来下面的问题:
依赖注入(DI)最终的目的就是解除上面我们所说的依赖,从而实现松耦合的软件架构体系,并且它延用了控制反转的思想,扩展为依赖关系的反转,即将“依赖关系”(具体可以说是创建依赖对象)这件事,从应用程序中转移到框架之中,这样一来应用程序就不必通过硬编码的形式创建对象,而是由框架提供,从而降低与引用的类型的依赖程度。
依赖注入本身和控制反转一样,都并不属于某个编程语言的特定领域,而是属于一种软件设计模式,在不同的开发领域有不同的体现形式,例如.NET Core中内置的依赖注入框架、还有Java的Spring、第三方的PicoContainer等等框架。
在我们应用程序代码中通过硬编码“new”的方式是一种产生依赖的对象创建方式,为了解除这种依赖方式,依赖注入框架为我们提供了一个叫做“容器”的概念,我们应用程序的对象将由“容器”为我们创建并提供给我们。
在依赖注入的术语当中,对于容器提供的对象我们统称为“服务”,服务包括服务类型和服务实例。
由于依赖注入容器是根据服务类型来获取服务实例的,所以要为某个类型提供服务,则必须先对服务进行注册,注册的服务类型我们通常定义为“接口或基类”以此将依赖关系抽象化。注册时除了服务类型外,还必须指定服务的一个具体实现类,并且注册的时候还需要指定这个服务的生命周期。
应用程序在完成定义和注册工作后,对于服务对象的创建和提供则完全交给框架的“容器”来完成。
“容器”在“机械齿轮”的情景模拟中容器就相当于一个第三方的齿轮,起到了一种“粘合剂”的作用,它不直接参与到应用程序的业务功能代码中,而是由框架负责运行。这样一来,不仅降低了对象之间的耦合程度,还能为各个类型主动提供所依赖的对象。

在很多地方都看到一种说法是:“依赖注入是实现控制反转的一种方式”。在经过大量的资料查阅之后我们发现,控制反转主要体现的是一种“任务流程控制权”的反转,而依赖注入则是体现的一种“对象创建权”的反转,更为抽象的说法应该是“对象依赖关系”的反转,这表明它们在反转的“事物”上存在着差异,但是它们反转的双方对象都是一致的,即从应用程序中转移到框架中。
所以基于上诉的分析,我个人认为控制反转和依赖注入并不是一种等价的概念,所以说“依赖注入是实现控制反转的一种方式”不是很恰当。这可以从软件开发教父Martin Fowler说发表文章中的一段话中区分开来,其中这段话翻译过来大致的含义如下:
当这些容器谈论它们如何如此有用,因为它们实现了“控制反转”时,我最终感到非常困惑。控制反转是框架的一个共同特征,所以说这些轻量级容器是特殊的,因为它们使用反转控制,就像说我的车是特殊的,因为它有轮子。
对于这些概念性的技术点,其实通过文字是很难下定义的。这不外乎和我们中国的传统文化一样,你问别人何为“道”?“道可道,非常道”,那可能10个人中会有9种解释。
基于客观性,我个人根据学习总结,对控制反转和依赖注入之间的理解是:控制反转是一个相对于笼统的说法,这就像张三开发的Web应用是面向对象的程序,李四开发的WPF桌面应用也属于面向对象程序,面向对象只不过是一个程序设计的基本。而依赖注入也是将“控制反转”做为一个框架的基本思想从而设计出来的一套用于实现“依赖关系反转”的应用框架。
有兴趣的朋友可以查阅Martin Fowler发表的一篇关于控制反转和依赖注入的经典文章进行深入研究:
https://www.martinfowler.com/articles/injection.html#ConcludingThoughts
对于每个需要依赖注入容器提供对象的类而言,都需要为依赖注入容器提供注入的形式,也就是告诉容器通过什么样的方式将对象传递给你,只有提前定义好了注入形式,容器才能对其进行依赖对象的注入。
依赖注入对于设计模式层面而言,其中注入的形式分为三种:1.接口注入、2.设置器注入、3.构造函数注入,对于不同的依赖注入框架而言其中的注入形式也存在差异,本文目前只解释.NET Core默认支持的构造函数注入的形式进行介绍。
构造函数注入就是:依赖注入容器将依赖的对象作为构造函数的参数传递到相应的类中。如下面的代码片段所示,Person类中依赖一个IHouse接口类型,而IHouse的实例则通过构造函数中对应类型的参数进行赋值。
1 public class Person
2 {
3 public IHouse House {get;}
4 public Person(IHouse house)
5 {
6 House=house;
7 }
8 }
使用构造器注入方式需要考虑到一个问题,因为构造函数会存在多个,存在多个构造函数情况下,依赖注入容器又会选择哪一个呢?容器对于这种选择情况,实际上在不同的依赖注入框架种会存在不同的选择策略,所以要根据采用的依赖注入框架而定。那么对于.NET Core而言,它会在所有构造函数的参数列表进行查找,看看哪个构造函数的参数列表是一个“超集”,如果存在“超集”那就会选择这个“超集”所对应的构造函数,对于这个超集的逻辑后续会详细展开,这里只做一个初步的了解即可。
1 public class Person
2 {
3 public IHouse House {get;}
4 public ICar Car {get;}
5
6 public Person(IHouse house)=>House=house;
7 public Person(IHouse house,ICar car):this(house)=>car=Car; //超集
8 }
依赖注入在提供某个服务对象时,该服务对象中往往存在着对其他服务对象的依赖,服务对象中的字段或属性是依赖的一种主要体现形式。依赖注入容器会根据这种“依赖链”提供所有间接或直接依赖的服务实例。
如果类型A中具有一个B类型的字段或属性,那么就代表类型A对类型B产生了依赖,这就属于一种直接的依赖。如果类型B中还存在一个C类型的字段或属性,那么类型A对类型C产生的依赖属于间接依赖,并且类型C后面间接或直接依赖的类型对于类型A而言都是间接依赖。依赖注入容器在使用的时候,不光对类型A直接依赖的类型进行对象的提供,并且对类型A所有间接依赖的类型也同样会进行对象提供。
例如下图包含了Person的直接依赖和间接依赖:

上图中Computer类对象依赖于Displayer类,所以Displayer类成了Person类的间接依赖。那么对于依赖注入容器而言,如果要提供Person类的对象,那么它直接和间接依赖的对象Computer、Displayer都会预先被初始化并自动注入到Person类的对象之中。基于这种注入的特点,我们可以简单地理解“依赖注入”属于一种针对依赖字段或属性的自动初始化方式。
存在间接依赖的注意事项
如果直接依赖的服务还存在其他依赖的服务(也就是对于当前提供的服务存在间接依赖),那么直接依赖的服务中需要提供对间接依赖服务注入的构造函数,否则这个间接服务会为NULL。
还是以上面的类图为例,如果要提供Person类的服务,Pseson类中就需要提供对Computer服务注入的构造函数。但是由于直接依赖的Computer类还存在对Displayer类的依赖,即Person的间接依赖,那么Computer类中还必须提供对Displayer服务注入的构造函数,不然这个Displayer间接服务的值会为NULL。
对于开发某个新的功能,当某个类需要使用外部依赖类型的对象时,使用依赖注入的流程步骤大致如下:

依赖注入这个技术知识点仅从理论上也很难直观的感受到它的“魅力”,接下来我打算通过代码示例的形式,让大家体验感受下依赖注入在ASP.NET Core中简单的应用形式。

我们根据上面的类图作为示例的背景,创建一个ASP.NET Core MVC的应用。针对Home控制器中的依赖项computer字段使用依赖注入的方式创建对象。通常在依赖注入应用场景下,依赖项的类型都定义为接口或基类,所以Home控制器依赖的computer字段类型定义为了一个接口。该接口有一个实现类为DellComputer,它会在服务注册时进行使用,DellComputer类的对象会赋值给computer字段。
接下来我们根据类图实现具体的编码步骤:
1.新建ASP.NET Core MVC的项目,并编码实现相应的类型,由于逻辑比较简单,所以类型都写在一个文件中,代码如下:
1 namespace DependencyInjectionDemo
2 {
3 public interface IComputer
4 {
5 string SayHi(); //打招呼
6 }
7
8 public class Computer : IComputer
9 {
10 public string SayHi()
11 {
12 return "你好,我是戴尔电脑";
13 }
14 }
15
16 }
2.在Home控制器中将IComputer接口类型的字段作为依赖项,并使用构造函数作为依赖注入的方式,然后在控制器的Index方法中使用依赖项中的SayHi方法,将该方法的返回值输出到视图。
1 public class HomeController : Controller
2 {
3 private readonly IComputer computer;
4
5 public HomeController(IComputer computer)
6 {
7 this.computer = computer;
8 }
9
10 public IActionResult Index()
11 {
12 ViewBag.Msg = this.computer.SayHi();
13 return View();
14 }
15
16 }
3.在Index视图中输出后台控制器中设置的ViewBag数据,该数据来源于Home控制器的computer字段,也就是它的依赖项。

4.在Startup.cs类的ConfigureServices方法中对Person类所依赖的IComputer服务进行注册。使用AddSingleton进行服务注册,第一个泛型参数为服务类型,第二个泛型参数为服务的实现类。该方法还决定了服务对象的生命周期,对于生命周期后面会有专题进行详细说明,目前无需纠结。此步骤实际上就是告诉容器,我的应用程序需要使用IComputer服务,并且具体的服务实现类是Computer类。
1 public void ConfigureServices(IServiceCollection services)
2 {
3 services.AddSingleton<IComputer, Computer>();
4 }
5.在完成了服务注册后,对于当前示例的依赖注入运用已经构建完成,我们可以运行项目查看效果。

上图中成功的输出了Home控制器依赖项(computer字段)的方法,并且在Home控制器中使用computer字段并没有通过“new”的形式对其实例化,就实现了对一个引用类型的方法调用。实际操作体验后你会发现,在ASP.NET Core中使用依赖注入并没有什么难点,但这其中的作用其实都是归功于依赖注入框架。
依赖注入通常对于一些初学者来说,它们在实际的项目中都是“无感”的,并且在面对日常的开发工作而言使用起来也很简单。这种现象本身就是一个优秀框架的设计体现之处,让一些复杂的东西从应用程序代码中转移到框架中,这会让运用框架的应用程序变得易于开发、易于扩展。
本文内容只能依赖注入做一个基本的介绍,如果想要完全掌握依赖注入框架这只是一个开头,后续在ASP.NET Core应用方面还有很多的细节点,例如服务的生命周期模式、注册和消费、反模式等等。
虽然依赖注入在应用程序中不会涉及很多代码量,但是它是ASP.NET Core框架的基石,整个ASP.NET Core都建立在一个依赖框架之上。所以掌握好依赖注入是你对ASP.NET Core起码的诚意,也是.NET开发者基本的素质。
我正在尝试修改当前依赖于定义为activeresource的gem:s.add_dependency"activeresource","~>3.0"为了让gem与Rails4一起工作,我需要扩展依赖关系以与activeresource的版本3或4一起工作。我不想简单地添加以下内容,因为它可能会在以后引起问题:s.add_dependency"activeresource",">=3.0"有没有办法指定可接受版本的列表?~>3.0还是~>4.0? 最佳答案 根据thedocumentation,如果你想要3到4之间的所有版本,你可以这
我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("
电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。 准备工作: 1、U盘一个(尽量使用8G以上的U盘)。 2、一台正常联网可使用的电脑。 3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。 4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。 U盘启动盘制作步骤: 注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注
在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList()Obt
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称
最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总
深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal
在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主
需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc