依赖注入(Dependency Injection,简称 DI)是一种设计模式,用于减少类之间的耦合度,提升代码的灵活性和可测试性。它的基本思想是将一个对象所依赖的资源(即依赖项)注入到该对象中,而不是让对象自己去创建这些依赖项。
一、依赖注入的主要概念
1、依赖项(Dependency):
依赖项是一个类或组件,它提供了某些功能或服务,供其他类使用。例如,在代码中,写的一些接口 IxxxxService、ILogger 和 IMapper 都是依赖项。
2、注入(Injection):
注入是将依赖项传递给依赖它的对象的过程。通常,依赖注入通过构造函数注入、属性注入或方法注入实现。构造函数注入是最常见和推荐的方式。
3、依赖注入容器(DI Container):
这是一个负责管理和提供依赖项的框架。在 ASP.NET Core 中,内置了一个 DI 容器,负责处理对象的创建和依赖项的注入。
二、依赖注入的优点
1、减少耦合度:
使用依赖注入可以使一个类不再直接依赖于它所使用的具体实现。相反,它依赖于抽象(例如接口),这样可以在运行时注入不同的实现。这使得代码更易于维护和修改,因为可以替换依赖项而不需要修改类的代码。
2、提高可测试性:
依赖注入使得单元测试变得更加容易,因为可以在测试中使用 mock 对象或替代实现来替代实际的依赖项,从而隔离测试的对象。
3、促进代码的复用:
通过将依赖项分离到外部,你可以更容易地重用类或组件,因为它们不再关心如何创建依赖项。
4、改进代码的可维护性:
随着系统的复杂性增加,依赖注入使得代码的维护和扩展变得更加简单和清晰。
三、依赖注入的方式
1. 构造函数注入
构造函数注入是最常用的依赖注入方式,它通过构造函数将依赖项传递给对象。这样,可以确保在对象创建时所有的依赖项都已准备好,并且依赖项在对象生命周期内不可变更。
示例:
假设在开发一个订单处理系统,有一个 OrderService 类,它依赖于一个 IOrderRepository 进行数据访问:
public interface IOrderRepository
{
void SaveOrder(Order order);
}
public class OrderRepository : IOrderRepository
{
public void SaveOrder(Order order)
{
// 保存订单到数据库
}
}
public class OrderService
{
private readonly IOrderRepository _orderRepository;
public OrderService(IOrderRepository orderRepository)
{
_orderRepository = orderRepository;
}
public void ProcessOrder(Order order)
{
// 处理订单
_orderRepository.SaveOrder(order);
}
}
在这种情况下,使用依赖注入容器来提供 IOrderRepository 的实现:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOrderRepository, OrderRepository>(); // 瞬态服务
services.AddTransient<OrderService>(); // 瞬态服务
}
}
在控制器中,可以通过构造函数注入 OrderService:
public class OrdersController : ControllerBase
{
private readonly OrderService _orderService;
public OrdersController(OrderService orderService)
{
_orderService = orderService;
}
[HttpPost]
public IActionResult CreateOrder(Order order)
{
_orderService.ProcessOrder(order);
return Ok();
}
}
2. 属性注入
属性注入是通过公共属性将依赖项传递给对象。这种方式通常不如构造函数注入推荐,因为它可能使依赖项的管理变得不那么明确,且允许在对象创建后更改依赖项。
示例:
public class OrderService
{
public IOrderRepository OrderRepository { get; set; }
public void ProcessOrder(Order order)
{
// 处理订单
OrderRepository.SaveOrder(order);
}
}
在实际使用中,可以这样配置依赖注入:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IOrderRepository, OrderRepository>();
services.AddTransient<OrderService>();
}
}
然后在使用 OrderService 时,DI 容器会自动注入 IOrderRepository:
public class OrdersController : ControllerBase
{
private readonly OrderService _orderService;
public OrdersController(OrderService orderService)
{
_orderService = orderService;
// 这里可以直接使用 _orderService
}
[HttpPost]
public IActionResult CreateOrder(Order order)
{
_orderService.ProcessOrder(order);
return Ok();
}
}
3. 方法注入
方法注入是在调用方法时将依赖项作为参数传递。这种方式可以用于动态提供依赖项的场景,但通常不如构造函数注入清晰。如下,OrderService想使用IOrderRepository,通过新建一个ProcessOrder方法来使用IOrderRepository 中的SaveOrder方法。
示例:
public class OrderService
{
public void ProcessOrder(Order order, IOrderRepository orderRepository)
{
// 处理订单
orderRepository.SaveOrder(order);
}
}
在控制器中调用方法时,依赖项可以通过参数传递:
public class OrdersController : ControllerBase
{
private readonly IServiceProvider _serviceProvider;
public OrdersController(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
[HttpPost]
public IActionResult CreateOrder(Order order)
{
var orderRepository = _serviceProvider.GetService<IOrderRepository>();
var orderService = new OrderService();
orderService.ProcessOrder(order, orderRepository);
return Ok();
}
}
四、依赖注入的实际应用
1、提升模块化:通过依赖注入,可以更容易地创建模块化的应用程序。例如,可以将不同的业务逻辑分解成多个服务,并使用 DI 容器进行管理。
2、方便测试:依赖注入使得单元测试变得更容易,因为可以通过替代实现(mock)来测试某个类,而无需依赖于具体的依赖项。
示例:
public class OrderServiceTests
{
[Fact]
public void ProcessOrder_ShouldSaveOrder()
{
// Arrange
var mockRepository = new Mock<IOrderRepository>();
var orderService = new OrderService(mockRepository.Object);
var order = new Order();
// Act
orderService.ProcessOrder(order);
// Assert
mockRepository.Verify(r => r.SaveOrder(order), Times.Once);
}
}
3、实现控制反转(IoC):依赖注入是控制反转的一种实现方式。通过将依赖项的管理交给外部容器,可以实现更灵活的应用架构。
五、总结
依赖注入是一种强大的设计模式,通过将对象的创建和管理职责转移到外部容器中,可以使得代码更为灵活、可维护和可测试。通过构造函数注入、属性注入和方法注入等不同方式,依赖注入帮助开发者构建解耦的系统,从而提高代码质量和开发效率。