12. 依赖注入/控制反转
📝 模块更新日志
12.1 依赖注入
所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。
通俗来讲,就是把有依赖关系的类放到容器中,然后在我们需要这些类时,容器自动解析出这些类的实例。
依赖注入最大的好处是实现类的解耦,利于程序拓展、单元测试、自动化模拟测试等。
依赖注入的英文为:Dependency Injection
,简称 DI
12.2 控制反转
控制反转只是一个概念,也就是将创建对象实例的控制权(原本是程序员)从代 码控制权剥离到 IOC 容器
中控制。
控制反转的英文为:Inversion of Control
,简称 IOC
12.3 IOC/DI
优缺点
传统的代码,每个对象负责管理与自己需要依赖的对象,导致如果需要切换依赖对象的实现类时,需要修改多处地方。同时,过度耦合也使得对象难以进行单元测试。
-
优点
- 依赖注入把对象的创建交给外部去管理,很好的解决了代码紧耦合(tight couple)的问题,是一种让代码实现松耦合(loose couple)的机制
- 松耦合让代码更具灵活性,能更好地应对需求变动,以及方便单元测试
-
缺点
- 目前主流的
IOC/DI
基本采用反射的方式来实现依赖注入,在一定程度会影响性能
- 目前主流的
在本章节不打算细讲 依赖注入/控制反转
具体实现和应用场景,想了解更多知识,可查阅 【ASP.NET Core 依赖注入】 官方文档。
12.4 依赖注入的三种方式
12.4.1 构造方法注入
目前构造方法注入是依赖注入推荐使用方式。
-
优点
- 在构造方法中体现出对其他类的依赖,一眼就能看出这个类需要依赖哪些类才能工作
- 脱离了 IOC 框架,这个类仍然可以工作
- 一旦对象初始化成功了,这个对象的状态肯定是正确的
-
缺点
- 构造函数会有很多参数
- 有些类是需要默认构造函数的,比如 MVC 框架的 Controller 类,一旦使用构造函数注入,就无法使用默认构造函数
- 这个类里面的有些方法并不需要用到这些依赖
代码示例:
public class FurionService
{
private readonly IRepository _repository;
public FurionService(IRepository repository)
{
_repository = repository;
}
}
12.4.2 属性方式注入
在 Furion
新版本中,已经移除属性注入功能,建议使用构造函数或方法方式注入,也可以通过 App.GetService<TService>
方式注入。
通过属性方式注入容易和类的实例属性混淆,不建议使用。
-
优点
- 在对象的整个生命周期内,可以随时动态的改变依赖
- 非常灵活
-
缺点
- 对象在创建后,被设置依赖对象之前这段时间状态是不对的
- 不直观,无法清晰地表示哪些属性是必须的
public class FurionService
{
public IRepository Repository { get; set; }
}
12.4.3 方法参数注入
方法参数注入的意思是在创建对象后,通过自动调用某个方法来注入依赖。
-
优点:
- 比较灵活
-
缺点:
- 新加入依赖时会破坏原有的方法签名,如果这个方法已经被其他很多模块用到就很麻烦
- 与构造方法注入一样,会有很多参数
public class FurionService
{
public Person GetById([FromServices]IRepository repository, int id)
{
return repository.Find(id);
}
}
12.5 注册对象生存期
12.5.1 暂时/瞬时
生存期
暂时生存期服务是每次从服务容器进行请求时创建的。 这种生存期适合轻量级、 无状态的服务。
在处理请求的应用中,在请求结束时会释放暂时服务。
通常我们使用 ITransient
接口依赖表示该生命周期。
12.5.2 作用域
生存期
作用域生存期服务针对每个客户端请求(连接)创建一次。在处理请求的应用中,在请求结束时会释放有作用域的服务。
通常我们使用 IScoped
接口依赖表示该生命周期。
12.5.3 单例
生存期
在首次请求它们时进行创建,之后每个后续请求都使用相同的实例。
通常我们使用 ISingleton
接口依赖表示该生命周期。
想了解更多 服务生存期
知识可查阅 ASP.NET Core - 依赖注入 - 服务生存期 章节。
12.6 内置依赖接口
Furion
框架提供三个接口依赖分别对应不同的服务生存期:
ITransient
:对应暂时/瞬时作用域服务生存期IScoped
:对应请求作用域服务生存期ISingleton
:对应单例作用域服务生存期
以上三个接口只能实例类实现,其他静态类、抽象类、及接口不能实现。
12.7 常见使用
12.7.1 第一个例子
创建 IBusinessService
接口和 BusinessService
实现类,代码如下:
using Furion.Core;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
namespace Furion.Application
{
public interface IBusinessService
{
Person Get(int id);
}
public class BusinessService : IBusinessService, ITransient
{
private readonly IRepository<Person> _personRepository;
public BusinessService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
}
public Person Get(int id)
{
return _personRepository.Find(id);
}
}
}
创建 PersonController
控制器,代码如下:
using Furion.Application;
using Microsoft.AspNetCore.Mvc;
namespace Furion.Web.Entry.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PersonController : ControllerBase
{
private readonly IBusinessService _businessService;
public PersonController(IBusinessService businessService)
{
_businessService = businessService;
}
[HttpGet]
public IActionResult Get(int id)
{
var person = _businessService.Get(id);
return new JsonResult(person);
}
}
}
例子解说
Furion
框架提供了非常灵活且方便的实现依赖注入的方式,只需要实例类继承对应生存期的接口即可,这里继承了 ITransient
,也就表明了这是一个 暂时/瞬时
作用域实例类。该类就可以作为被注入对象,同时也能注入其他接口对象。
上面的例子中,BusinessService
注入了 IRepository<Person>
仓储接口,同时 PersonController
控制器注入了 IBusinessService
接口。
这样 PersonController
和 BusinessService
之间就实现了解耦,不再依赖于具体的 BusinessService
实例。
这就是依赖注入/控制反转最经典的例子。
12.7.2 注册泛型实例
创建 IBusinessService<T>
接口和 BusinessService<T>
实现类,代码如下:
using Furion.Core;
using Furion.DatabaseAccessor;
using Furion.DependencyInjection;
namespace Furion.Application
{
public interface IBusinessService<T>
{
Person Get(int id);
}
public class BusinessService<T> : IBusinessService<T>, ITransient
{
private readonly IRepository<Person> _personRepository;
public BusinessService(IRepository<Person> personRepository)
{
_personRepository = personRepository;
}
public Person Get(int id)
{
return _personRepository.Find(id);
}
}
}
创建 PersonController
控制器,代码如下:
using Furion.Application;
using Microsoft.AspNetCore.Mvc;
namespace Furion.Web.Entry.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class PersonController : ControllerBase
{
private readonly IBusinessService<int> _businessService;
public PersonController(IBusinessService<int> businessService)
{
_businessService = businessService;
}
[HttpGet]
public IActionResult Get(int id)
{
var person = _businessService.Get(id);
return new JsonResult(person);
}
}
}