.NET Core中的IoC和DI

1 本期文章介绍

  • IoC和DI的简单含义
  • IoC的理解与认知
  • DI的理解与认知
  • IoC与DI的关系
  • 使用IoC/DI的好处
  • 在.NET Core中使用IoC/DI
  • 本文示例代码下载地址

 

.NET Core中的IoC和DI_第1张图片


2 往期文章

  • 使用LayUI+EFCore+AutoMapper+LINQ完成一个简单的增删改查
  • 如何在.NET Core中使用AutoMapper
  • 优雅的LINQ之美
  • C#中的虚方法,抽象方法,抽象类以及接口

3 IoC和DI的简单含义
IoC,意为控制反转,英文(Inversion of Control),它不是一种技术,而是一种设计思想,一个重要的面向对象编程的法则。IoC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
DI,意为依赖注入,英文(Dependency Injection)。组件之间的依赖关系由容器在运行期间决定,即由容器动态地将依赖项注入到组件当中,通过依赖注入机制,我们有时候只需要简单地配置,无需修改代码即可完成自身逻辑,而不必去关心依赖的具体资源,姓甚名谁,由何处来,又去往何处。
4 IoC的理解与认知
我们先来思考几个问题,既然是叫IoC(控制反转),那么

  • 是谁控制谁?
  • 控制了什么?
  • 为什么叫反转?难道还有正转?

我们一个一个来进行分析。

  • 是谁控制谁?控制了什么?传统程序当中,我们定义的一个A类,需要另一个B类时,直接就在A类的内部通过new进行创建依赖的b对象了,是我们的程序主动去创建依赖对象。而IoC它的核心思想就是有一个容器专门来创建这些依赖的对象,即由IoC容器来控制依赖的对象的创建。谁控制了谁?那当然是IoC容器控制了对象;控制了什么?那就是控制了外部资源的获取(不单单是依赖对象的创建。还有什么文件资源啊等等的)
  • 为什么是反转,而不是正传?在传统应用程序当中,是由我们自己再对象内部去直接创建获取依赖的对象,也就是正转;而反转则是由容器来帮忙去创建对象及注入依赖对象,对象只是被动的接收依赖对象,所以是反转。哪些方面被反转了?依赖对象的获取方式被反转了。以前是主动出击,实例化依赖对象,现在是注入依赖对象,被动接受。
  • 其实IoC容器,它就相当于一个专门来创建对象的工厂,你要什么对象,他就给你什么对象。有了IoC容器,依赖关系就变了,原先的依赖关系就没有了,他们都依赖于IoC容器了,通过IoC容器来建立他们之间的关系。下面通过几个图示例子来解析IoC思想。

 

.NET Core中的IoC和DI_第2张图片
  • 传统程序设计当中,用户类依赖于用户信息类,都是主动去创建相关对象再组合起来,客户端向服务器发送请求之后经历了这三个过程:用户类的创建、用户信息类的创建,将用户信息类主动注入到用户类。

 

.NET Core中的IoC和DI_第3张图片
  • 而当有了IoC/DI容器后,客户端获取这些服务,不再主动去索取了,不再主动去创建这些对象了。IoC会做这些动作:创建用户类,看用户类是否有依赖对象,有的话,首先创建依赖对象,之后再将其注入到用户类当中。由容器掌管这些对象的生命周期。
  • 我们可以再试想一下,在一个大型项目一个模块当中,有这样若干个类,就4个吧,object a,b,c,d,a依赖于b,b依赖于c,c依赖于d,甚至还可能交叉依赖,这时候我们引入了第三方“IoC”,使得a,b,c,d这四个对象之间没有了耦合关系,如下图:齿轮之间的传动全部依靠“第三方”。全部对象的控制权上缴给IoC,由IoC来分配对象(-。-)所以,IoC容器就成了整个系统的核心,它类似于粘合剂,把所有的对象都给粘合在了一起,这样子相辅相成,发挥粗最大的作用。如果没有它,对象彼此之间就失去了联系。

 

.NET Core中的IoC和DI_第4张图片

5 DI的理解与认知
同样的,在学习DI之前,我们先来思考这样几个问题,既然是叫DI(依赖注入),那么:

  • 是谁依赖于谁?
  • 为什么需要依赖?
  • 谁注入谁?
  • 注入了什么?

下面我们来一一分析

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源
  • 谁注入谁:很明显是IoC容器注入应用程序
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、数据等等)

依赖注入,通常有两种方式:

  • 设置注入
  • 构造注入

通常来说,依赖注入是为了控制反转,就是说不需要我们自己去new服务实例了。
6 IoC与DI的关系

  • 他们之间的关系其实是同一个概念的不同角度描述,依赖注入是为了实现控制反转,“依赖注入”明确描述了“被注入对象依赖IoC容器,配置依赖对象”。

7 IoC/DI的好处

  • 没有引入IOC之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,A直接使用new关键字创建B的实例,程序高度耦合,效率低下,无论是创建还是使用B对象,控制权都在自己手上。
  • 传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改很多地方。同时,过度耦合也使得对象难以进行单元测试。
  • 依赖注入把对象的创造交给容器去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制。
  • 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试

说了这么多,我们来实战一下IoC/DI
8 在.NET Core中使用依赖注入

  • 新建Web Core API应用程序,删除自带的weather控制器和类,新建空api控制器Student,新建方法,返回学生的问候语,并且启动监听,dotnet watch run,以便调试:

 

.NET Core中的IoC和DI_第5张图片
  • 浏览器中输入:localhost:5000/api/Student,测试如下图:

 

.NET Core中的IoC和DI_第6张图片
  • 在控制器当中,我们返回了一个学生的姓名。但是在实际开发当中,我们的业务逻辑部分是和控制器分开的,于是,我们新建一个仓储类库Repository,新建仓储服务类StudentRepository,新增业务方法,返回学生问候语:

 

.NET Core中的IoC和DI_第7张图片
  • 现在问题来了,随着实际业务开发,我们的业务类当中的方法会越来越多,同名或者类似的方法数不胜数,我们直接定位进去就是一大堆方法和方法体,所以我们修改一下,根据面向接口编程原则,我们将其抽离,新建仓储接口类库,IRepository,新建接口类,IStudentRepository,原先的方法继承实现接口:

 

.NET Core中的IoC和DI_第8张图片
  • 更改控制器中的方法:

 

.NET Core中的IoC和DI_第9张图片
  • 测试如下:

 

.NET Core中的IoC和DI_第10张图片
  • 现在是很简单的一个demo,在实际当中,我们的业务层中依赖着众多的对象,比如数据库、日志、等等相关的一些资源,下面我们举个例子,在业务类StudentRepository中依赖着一个Other类型的对象,用于获取天气。

 

.NET Core中的IoC和DI_第11张图片

 

.NET Core中的IoC和DI_第12张图片
  • 那这样很明显,我们的控制器在new的时候,需要

 

.NET Core中的IoC和DI_第13张图片

 

 

.NET Core中的IoC和DI_第14张图片
  • 而在实际当中,我们的业务类是依赖了众多的对象,这只是一个控制器一个依赖对象,假如是很多个控制器,很多个依赖对象呢,这样设计明显不方便,而且不利于测试,接下来我们使用依赖注入。

 

.NET Core中的IoC和DI_第15张图片
  • 如上图,在Startup类的ConfigureServices方法当中注入。ConfigureServices这个东西是专门为我们的服务做准备工作的,怎么说呢,你警察执法需要警棍吧;大妈扫地需要扫帚吧,ConfigureServices里面其实就是为我们的服务注入所需要的工具,我们对象此时并没有说是被实例化,它只在你需要的时候,实例化,并注入到需要的对象当中去。
  • 举个生活不恰当的例子哈,嘿嘿。以前我们找男女朋友,都是直接上,谈恋爱,后来人多了,出现了婚介服务(IoC)容器。这个人啊,现在不需要自己去找女朋友了,你想要什么样的女朋友?去婚介所,中介所,登记一下资料,给出要求,交钱;他就在给你搜罗,你需要女朋友,他主动给你介绍,注入到你的生活当中去。这里中介在他们那登录的形形色色的人的资料,就存放在那。当你需要,他就new一个出来,现在不再是你主动出击,而是被迫接收了,当然你也可以拒绝哈哈,这就好比,程序,拟注入错了对象,我并不需要这个对象,是那种!
  • 我们使用services.AddScoped<接口类,实现类>()的方式注入,这个是net Core自带的IoC容器三大生命周期注入方式之一,为什么说自带的呢,因为后边我们还会用到第三方容器,另外两个分别是Singleton单例模式注入、Transient瞬时模式注入;
  • Singleton 每次首次请求之后,实例化,之后所有的请求都讲沿用这个服务对象。
  • Scoped 每次请求之后,都讲实例化一个对象,生命周期贯穿整次请求,每次请求使用的服务对象不是同一个。
  • Transient 服务在每次请求时被创建,它最好被应用于轻量级无状态服务(至于啥是轻量级无状态服务,嘿嘿,我也不知道。。。~)
  • 下面更改控制器实例方式,改为构造函注入。

 

.NET Core中的IoC和DI_第16张图片
  • 运行一下,结果如下图:

 

.NET Core中的IoC和DI_第17张图片
  • 可以看到,它报了一个错,不能够加载others这个服务,这是为什么呢;因为我们的业务类那里的构造函数注入了others这个对象,但是却没有在容器当中注入,接下来,我们将others进行一下注入:

 

.NET Core中的IoC和DI_第18张图片
  • 注意,ConfigureService里面的注入是不分先后顺序的,因为你无论是先给大妈发扫帚还是先给警察发警棍,这一切都是要准备就绪之后,整个应用才可以提供服务,因为这是准备工作,所有的准备就绪之后,服务才会开始运转,所以准备工作的先后顺序是不区分的。再次运行测试如下图:

 

.NET Core中的IoC和DI_第19张图片
  • 这时服务已经成功运行,那么现在还有一个问题,在实际当中,我们的接口和实现等服务肯定不止这一个,难道我们要service.add这样一直下去?不要担心,有我们的第三方容器,autofac
  • 给api层引入nuget包,Autofac.Extensions.DependencyInjection,如下图:

 

.NET Core中的IoC和DI_第20张图片
  • 在Program程序入口的CreateHostBuilder中添加autofac服务:

 

.NET Core中的IoC和DI_第21张图片
  • 接着在Startup新增ConfigureContainer(ContainerBuilder builder)如下图:

 

.NET Core中的IoC和DI_第22张图片
  • 注释掉之前在ConfigureService中的注入方法,并在接着在Startup新增ConfigureContainer方法中新增注入,如下图:

 

.NET Core中的IoC和DI_第23张图片
  • 唉~等等,怎么感觉比之前的注入方式还复杂些?别急,我们改为程序集注入,这样子它就自动找到对应的父类接口并注入:

 

.NET Core中的IoC和DI_第24张图片
  • 测试效果如下图:

 

.NET Core中的IoC和DI_第25张图片
  • 第一种注入程序集的方式,是我们直接通过反射加载程序集,但是可以看到,我们的api层还是依赖了很多层的,这里,我们解耦,删掉Repository层的引用,只引入对外暴露的接口层:IRepository

 

.NET Core中的IoC和DI_第26张图片
  • 这个时候如果你再运行,肯定会报错,因为你api层跟Repository层解耦了,你就没法再用反射Load Repository层的程序集了。接下来我们改为使用读取dll文件反射动态地创建实例注入Repository层的程序集:

 

.NET Core中的IoC和DI_第27张图片
  • 这个时候,我们还需要将Repository层的dll输出目录更改一下:

 

.NET Core中的IoC和DI_第28张图片
  • 运行测试一下:

 

.NET Core中的IoC和DI_第29张图片

完美成功,至此,本期的学习和分享就到这里结束了,多写,多思考,欢迎留言交流,共同进步,我们下期再见!

欢迎关注公众号:dotNET学习天地

一起学习和进步!

 

你可能感兴趣的:(C#,java,设计模式,编程语言,spring,面试)