当我初学.NET编程时,曾自信满满地认为已经完全掌握了依赖注入(DI)。构造函数注入、服务注册、作用域与单例——我无所不知!
直到某天生产环境突发重大事故:应用性能骤降、内存泄漏、行为异常。排查过程中发现的问题,瞬间击碎了我的自信泡沫。
我原以为懂DI,却从未真正理解其精髓。
本文将揭示99%开发者都不了解的DI深层知识,这些认知将让你的代码更易维护、便于测试且具备扩展性。
多数人对DI的理解停留在构造函数注入:
public class OrderService
{
private readonly IEmailService _emailService;
public OrderService(IEmailService emailService)
{
_emailService = emailService;
}
public void PlaceOrder(Order order)
{
// 业务逻辑
_emailService.SendConfirmation(order);
}
}
这确实是DI的一种形式。但DI远不止于此,还包括:
构造函数注入只是最佳实践,而非DI的全部内涵。
services.AddScoped<IUserContext, UserContext>();
services.AddSingleton<ReportGenerator>();
若将IUserContext注入ReportGenerator,运行时将抛出异常:
"无法从单例消费作用域服务"
解决方案:
当类构造函数注入5个以上服务时:
public MyController(IServiceA a, IServiceB b, IServiceC c, IServiceD d, IServiceE e)
这是典型的代码异味,因为:
解决方案:
我们常认为接口代表松耦合,但若接口仅有一个实现,那就是伪抽象:
public interface IEmailService
{
void Send(string to, string message);
}
public class SmtpEmailService : IEmailService
{
public void Send(string to, string message)
{
// SMTP实现
}
}
问题根源:
当需要切换SendGrid或MailGun时,全代码库仍依赖SmtpEmailService
解决方案:
public class OrderService
{
public void PlaceOrder()
{
var emailService = ServiceLocator.Get<IEmailService>();
emailService.Send("hello@example.com", "订单已创建");
}
}
这是典型的反模式,会导致隐藏依赖。
正解:坚持构造函数注入
组合根是集中注册所有依赖的地方,通常是Program.cs或Startup.cs:
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IEmailService, EmailService>();
保持组合根整洁能显著提升可维护性。
我曾以为DI就是创建接口并通过构造函数注入——如此简单。但这实际是个认知陷阱。依赖注入实则是一种哲学,一种让应用松耦合、易测试、高灵活性的设计思想。