.NET代码救星:依赖注入实战指南——从混乱到优雅的架构蜕变

作者:微信公众号:【架构师老卢】
6-7 8:27
19

我还记得那个意识到我们的.NET代码库就像一颗定时炸弹的时刻。

凌晨3点,我正在调试电商结算系统中一个“简单”的变更。本应20分钟完成的任务,却变成了一场通宵马拉松,原因如下:

  • 紧耦合导致每改一处就破坏三个其他功能
  • 静态方法地狱让单元测试寸步难行
  • 隐藏依赖项像地雷般遍布50多个类

第二天早晨,我向首席架构师——一位从.NET Framework 2.0时代就开始构建企业系统的资深老兵——大吐苦水。

他微微一笑,说道:

“你在和代码搏斗,是因为没让控制反转容器发挥作用。”

接下来,他给我上了一堂依赖注入的实战大师课——这个习惯最终将我们的意大利面条代码变成了可维护、可测试且高性能的系统。

以下是我们的具体改造方法(附性能提升证明)。

核心习惯:“无处不在的构造函数注入”

多数.NET开发者以为自己用了依赖注入,因为他们会写这样的代码:

// "伪DI"反模式  
public class OrderService  
{  
    private readonly ILogger _logger = new Logger();  
    private readonly IPaymentGateway _gateway = ServiceLocator.Resolve<IPaymentGateway>();  
}

问题在于:

  • 隐式依赖(OrderService究竟需要什么?)
  • 测试噩梦(无法模拟ServiceLocator调用)
  • 性能损耗(服务定位器未优化解析速度)

架构师的黄金法则:
“如果类需要某个依赖,就必须通过构造函数索要。没有例外。”

在他的严格要求下,我们实现了:

// "正确DI"示范  
public class OrderService  
{  
    private readonly ILogger _logger;  
    private readonly IPaymentGateway _gateway;  

    public OrderService(ILogger logger, IPaymentGateway gateway)  
    {  
        _logger = logger;  
        _gateway = gateway;  
    }  
}

为何有效?

  • 代码自文档化:依赖关系一目了然
  • 天然可测试:轻松模拟接口进行单元测试
  • 生命周期管理:由DI容器控制Scoped与Singleton

真实案例:改造前后对比

改造前(混乱状态)
场景:为结算系统添加风控检测

// 隐藏的静态依赖  
public void ProcessOrder(Order order)  
{  
    var fraudChecker = new FraudDetectionService();  
    if (fraudChecker.IsHighRisk(order))  
    {  
        Logger.Instance.LogWarning("欺诈行为!");  
        // ...  
    }  
}

问题:

  • 0%测试覆盖率(无法模拟FraudDetectionService)
  • 脆弱修改(若风控逻辑需要IUserRepository怎么办?)
  • 性能开销(每次调用都实例化服务)

改造后(清晰架构)

// 显式依赖  
public class OrderProcessor  
{  
    private readonly IFraudDetectionService _fraudService;  
    private readonly ILogger _logger;  
    public OrderProcessor(IFraudDetectionService fraudService, ILogger logger)  
    {  
        _fraudService = fraudService;  
        _logger = logger;  
    }  
    public void ProcessOrder(Order order)  
    {  
        if (_fraudService.IsHighRisk(order))  
        {  
            _logger.LogWarning("欺诈行为!");  
        }  
    }  
}

重构后的生产环境性能提升:
(数据来自电商API基准测试)

五大进阶DI模式

1. 装饰器模式处理横切关注点

替代遍地开花的日志代码:

// 为任何IOrderProcessor添加日志  
public class LoggingOrderProcessorDecorator : IOrderProcessor  
{  
    private readonly IOrderProcessor _inner;  
    private readonly ILogger _logger;  

    public LoggingOrderProcessorDecorator(IOrderProcessor inner, ILogger logger)  
    {  
        _inner = inner;  
        _logger = logger;  
    }  
    public void ProcessOrder(Order order)  
    {  
        _logger.LogInformation("订单处理中...");  
        _inner.ProcessOrder(order);  
        _logger.LogInformation("订单处理完成");  
    }  
}

注册方式:

services.AddTransient<IOrderProcessor, CoreOrderProcessor>();  
services.Decorate<IOrderProcessor, LoggingOrderProcessorDecorator>();

2. 选项模式配置管理

旧方式(脆弱):

var timeout = ConfigurationManager.AppSettings["PaymentTimeout"];

DI方式(类型安全):

// 1. 定义配置类  
public class PaymentOptions  
{  
    public int TimeoutSeconds { get; set; }  
}  

// 2. 注册  
services.Configure<PaymentOptions>(Configuration.GetSection("Payment"));  

// 3. 注入使用  
public PaymentService(IOptions<PaymentOptions> options)  
{  
    _timeout = options.Value.TimeoutSeconds;  
}

3. 工厂模式处理条件逻辑

// 接口定义  
public interface IPaymentGatewayFactory  
{  
    IPaymentGateway GetGateway(string countryCode);  
}  

// 实现  
public class PaymentGatewayFactory : IPaymentGatewayFactory  
{  
    private readonly IServiceProvider _provider;  
    public PaymentGatewayFactory(IServiceProvider provider)  
    {  
        _provider = provider;  
    }  
    public IPaymentGateway GetGateway(string countryCode)  
    {  
        return countryCode switch  
        {  
            "US" => _provider.GetRequiredService<StripeGateway>(),  
            "EU" => _provider.GetRequiredService<AdyenGateway>(),  
            _ => throw new NotSupportedException()  
        };  
    }  
}

4. 作用域与单例生命周期

(基于.NET 8的1万次请求基准测试)

5. 组合根验证

问题: 缺失的依赖直到运行时才报错
解决方案: 启动时验证

// 在Program.cs中  
var sp = services.BuildServiceProvider();  
sp.ValidateScopes(); // 当Scoped服务依赖Singleton时抛出异常  
sp.ValidateOnBuild(); // 任何依赖缺失时立即抛出  

维护收益

采用严格DI六个月后:

  • Bug报告减少62%(编译期依赖检查提前发现问题)
  • 新人上手时间减半(“只需遵循构造函数参数”)
  • 性能提升(合理的作用域使GC压力降低40%)

你的任务

  1. 审查一个服务:找到使用new或静态调用的类
  2. 重构它:通过构造函数显式声明所有依赖
  3. 体会变化:感受测试变得多么轻松

一旦全面采用DI,你会惊讶曾经没有它的日子是怎么过来的。

相关留言评论
昵称:
邮箱:
阅读排行