是现代软件开发中的一个核心概念,尤其在构建可维护、可测试、松耦合的系统时显得非常重要。依赖注入是一种设计模式,用于将对象所依赖的其他对象(依赖项)通过外部方式传入,而不是在对象内部自己创建依赖。
通常有三种主要的注入方式(模式),适用于不同的场景和需求。
模式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
构造函数注入 | 强依赖,核心依赖 | 明确依赖,便于测试 | 构造函数参数多时不易维护 |
属性注入 | 可选依赖,插件式设计 | 灵活 | 可能出现未初始化的依赖 |
方法注入 | 临时依赖,工具类 | 简洁,不影响类结构 | 依赖不易复用,测试略复杂 |
构造函数注入(Constructor Injection)
🎯 场景:日志记录服务
我们有一个 ILogger
接口,有两个实现类:ConsoleLogger
和 FileLogger
。我们将通过构造函数将 ILogger
注入到业务类中。
✅ 第一步:定义接口和实现类
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[Console] {message}");
}
}
✅ 第二步:业务类使用构造函数注入
public class OrderService
{
private readonly ILogger _logger;
// 构造函数注入
public OrderService(ILogger logger)
{
_logger = logger;
}
public void PlaceOrder()
{
// 业务逻辑
_logger.Log("订单已成功创建。");
}
}
✅ 第三步:手动注入依赖并运行
class Program
{
static void Main(string[] args)
{
ILogger logger = new ConsoleLogger(); // 创建依赖
OrderService service = new OrderService(logger); // 注入依赖
service.PlaceOrder();
}
}
🧠 构造函数注入的优点
- 强制依赖:必须在构造时提供依赖,避免运行时出错。
- 不可变性:依赖通常是
readonly
字段,增强安全性。 - 适合核心依赖:如数据库连接、日志服务等。
属性注入(Property Injection)
属性注入是通过设置公共属性来注入依赖。
✅ 示例代码:
public interface ILogger
{
void Log(string message);
}
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($"[Log] {message}");
}
}
public class ReportService
{
// 属性注入
public ILogger Logger { get; set; }
public void GenerateReport()
{
Logger?.Log("Report generated.");
}
}
使用方式:
var service = new ReportService();
service.Logger = new ConsoleLogger(); // 手动注入
service.GenerateReport();
在使用依赖注入容器时,属性注入通常需要额外配置或使用特定框架支持(如 Autofac、Unity)。
方法注入(Method Injection)
方法注入是通过方法参数传入依赖。
✅ 示例代码:
public class NotificationService
{
public void Notify(string message, ILogger logger)
{
logger.Log($"Notification: {message}");
}
}
使用方式:
var logger = new ConsoleLogger();
var service = new NotificationService();
service.Notify("System update completed.", logger);
依赖注入的生命周期
生命周期类型 | 创建时机 | 生命周期范围 | 适用场景 |
---|---|---|---|
Singleton(单例) | 第一次请求时创建 | 应用程序整个生命周期 | 配置类、缓存、日志等 |
Scoped(作用域) | 每个请求创建一次 | 每个 HTTP 请求或作用域 | Web 应用中每个请求独立的数据 |
Transient(瞬时) | 每次请求都创建 | 无状态、短生命周期 | 轻量级服务、无状态逻辑 |
Singleton
// 1、创建依赖注入(IOC容器)
ServiceCollection services = new ServiceCollection();
// 2、注册ProductService
services.AddSingleton<ProductService>();
// 2.1、注册ProductRepository
services.AddSingleton<ProductRepository>();
// 3、取对象(构造ServiceProvider)
var ServiceProvider = services.BuildServiceProvider();
// 4、取对象
ProductService productService = ServiceProvider.GetRequiredService<ProductService>();
productService.GetProduct();
Transient
// 1、创建依赖注入(IOC容器)
ServiceCollection services = new ServiceCollection();
// 2、注册ProductService
services.AddTransient<ProductService>();
// 2.1、注册ProductRepository
services.AddTransient<ProductRepository>();
// 3、取对象(构造ServiceProvider)
var ServiceProvider = services.BuildServiceProvider();
// 4、取对象
ProductService productService = ServiceProvider.GetRequiredService<ProductService>();
productService.GetProduct();
// 4、取对象(验证Transient)
ProductService productService1 = ServiceProvider.GetRequiredService<ProductService>();
productService1.GetProduct();
Scoped
// 1、创建依赖注入(IOC容器)
ServiceCollection services = new ServiceCollection();
// 2、注册ProductService
services.AddScoped<IProductService,ProductService>();
// 2.1、注册ProductRepository
services.AddScoped<IProductRepository,ProductRepository>();
// 3、取对象(构造ServiceProvider)
var ServiceProvider = services.BuildServiceProvider();
// 4、取对象
using (IServiceScope serviceScope = ServiceProvider.CreateScope())
{
// 1、第一次
IProductService productService = serviceScope.ServiceProvider.GetRequiredService<IProductService>();
productService.GetProduct();
// 1、第一次
IProductService productService1 = serviceScope.ServiceProvider.GetRequiredService<IProductService>();
productService1.GetProduct();
}
依赖注入:使用Autofac
属性依赖注入
// 1、创建ContainerBuilder
ContainerBuilder containerBuilder = new ContainerBuilder();
containerBuilder.RegisterType<ProductService>().As<IProductService>().PropertiesAutowired();
containerBuilder.RegisterType<CSProductService>().As<IProductService>().PropertiesAutowired();
containerBuilder.RegisterType<ProductRepository>().As<IProductRepository>();
// 2、构造容器
var Container = containerBuilder.Build();
// 3、取对象
using (var scope = Container.BeginLifetimeScope()) {
IProductService productService = scope.Resolve<IProductService>();
productService.GetProduct();
}
批量注册
// 1、创建ContainerBuilder
ContainerBuilder containerBuilder = new ContainerBuilder();
// 1.1、整个项目注册
containerBuilder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())// 加载整个项目
.AsImplementedInterfaces() // 只要有接口都实现注册
.PropertiesAutowired(); // 开发属性依赖注入
// 2、构造容器
var Container = containerBuilder.Build();
// 3、取对象
using (var scope = Container.BeginLifetimeScope())
{
IProductService productService = scope.Resolve<IProductService>();
productService.GetProduct();
IOrderService orderService = scope.Resolve<IOrderService>();
orderService.GetOrder();
}
对象过滤
// 1、创建ContainerBuilder
ContainerBuilder containerBuilder = new ContainerBuilder();
// 1.1、整个项目注册
containerBuilder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())// 加载整个项目
.Where(t=> t.GetInterfaces().Any(i => i == typeof(IInject))) // 实现过滤
.AsImplementedInterfaces() // 只要有接口都实现注册
.PropertiesAutowired(); // 开发属性依赖注入
// 2、构造容器
var Container = containerBuilder.Build();
// 3、取对象
using (var scope = Container.BeginLifetimeScope())
{
IProductService productService = scope.Resolve<IProductService>();
productService.GetProduct();
IOrderService orderService = scope.Resolve<IOrderService>();
orderService.GetOrder();
}
🌟 依赖注入的重要性
1. 降低耦合度
不依赖具体实现,而是依赖接口(或抽象类)。
类之间的耦合降低,变得更灵活、更易于替换实现。
🔧 示例:
// 不用 DI 的代码(高耦合)
var logger = new FileLogger(); // 硬编码依赖
// 用 DI 的代码(低耦合)
public class MyService
{
private readonly ILogger _logger;
public MyService(ILogger logger) // 外部注入
{
_logger = logger;
}
}
2. 提高测试性(支持单元测试/Mock)
可以方便地替换依赖,比如用模拟对象(Mock)替代真实服务,便于单元测试。
🧪 示例:
注入一个假的数据库或服务,从而隔离测试逻辑,不依赖真实的外部系统。
3. 增强可维护性
修改依赖实现时,不需要修改使用它的类,只需修改配置或注入部分。
4. 支持开闭原则(OCP)
类对扩展开放,对修改关闭。通过依赖注入,可以新增实现而不需要修改原来的代码。
5. 易于管理生命周期
通过依赖注入容器(如 .NET 的 Microsoft.Extensions.DependencyInjection
或 Java 的 Spring),可以集中管理对象的创建和销毁。
🚀 应用场景
Web 应用程序控制器依赖服务组件。
日志记录、数据库访问、缓存管理等常用服务。
插件化系统中的动态模块加载。
使用 IoC 容器(如 Autofac、Unity、Spring、Guice 等)时。
💬 总结一句话:
依赖注入不是为了减少代码量,而是为了让代码更可扩展、可维护、可测试、可复用。