一、依赖注入基础:什么是依赖注入、控制反转(IoC)概念、为什么需要DI、DI的优缺点分析

1.1 从一个让我头疼的项目说起

我记得刚入行那会儿,接手过一个老项目。

那个项目里,业务逻辑层直接 new 数据访问层。看起来没什么问题,对吧?

但后来客户说:「数据库要从 SQL Server 换成 Oracle。」

好家伙,整个团队忙活了两个月。因为每一处 new SqlConnection 的地方都得改。改完还得重新测试,生怕漏掉哪个角落。

你想想看,这还只是换数据库。要是换日志组件、换缓存方案、换消息队列呢?

说白了,这种硬编码依赖的方式,让系统变得极其脆弱。每次改动都像拆炸弹。

这就是我们今天要聊的——依赖注入(DI)要解决的核心问题。

1.2 什么是依赖注入?

依赖注入,英文叫 Dependency Injection,简称 DI。

名字听着挺唬人,其实道理很简单:

一个类需要的依赖,不要自己创建,让别人给你送过来。

举个例子。假设你开了一家餐厅:

  • 没有 DI 的做法:厨师自己种菜、自己养猪、自己磨面粉。什么都自己干,累死不说,换个供应商得把厨房拆了重建。
  • 有 DI 的做法:厨师只管炒菜。菜有人送,肉有人供,面粉有人管。厨师只需要说「我要什么」,自然有人送上门。

在代码里,就是这样的区别:

// 没有 DI —— 类自己创建依赖
public class OrderService
{
    private readonly ILogger _logger;
    
    public OrderService()
    {
        // 自己 new 出来
        _logger = new FileLogger();
    }
}

// 有 DI —— 依赖从外部注入
public class OrderService
{
    private readonly ILogger _logger;
    
    public OrderService(ILogger logger)
    {
        // 别人给我,我只管用
        _logger = logger;
    }
}

看到区别了吗?第二种写法里,OrderService 不再关心 logger 是写文件、写数据库还是写控制台。它只管调用。

核心要点:依赖注入的本质,就是把「创建依赖」这件事,从类的内部移到外部。类只负责「使用」,不负责「创建」。

1.3 控制反转(IoC)—— 到底反转了什么?

聊 DI 就绕不开 IoC(控制反转)。这两个概念经常被混用,我简单说说我的理解。

控制反转,反转的是「控制权」。

传统写法里,控制权在类自己手里——我自己 new 依赖,自己决定用哪个实现。

用了 IoC 之后,控制权交给了外部容器。类不再主动创建依赖,而是被动等待注入。

打个比方:

  • 传统方式:你去饭店,自己进厨房炒菜。控制权在你手里。
  • IoC 方式:你坐下点菜,厨师做好端上来。控制权在厨师(容器)手里。

所以 IoC 是一种设计思想,DI 是实现这种思想的具体技术手段。

我的习惯:我一般把 IoC 看作「原则」,把 DI 看作「实践」。面试时如果有人问区别,我会说:IoC 是「我要什么你给我什么」,DI 是「你怎么给我的具体方式」。

1.4 为什么需要依赖注入?

这个问题,我用三个字回答:解耦合

但光说解耦合太抽象了。我具体说说我在项目中遇到的几个场景:

场景一:单元测试

没有 DI 的代码,测试起来特别痛苦。因为你要测 OrderService,就得连数据库、连文件系统、连网络。准备工作比测试本身还长。

有了 DI,我可以注入一个 Mock 的 ILogger,注入一个 Mock 的 IRepository。测试变得干净利落。

场景二:替换实现

我曾经有个项目,日志一开始用 log4net,后来要换成 Serilog。

没有 DI 的话,得满世界找 new log4net.Logger() 的地方。有 DI 的话,改一行注册代码就行。

场景三:生命周期管理

有些对象我们希望整个请求共用同一个实例(比如 DbContext),有些希望每次都用新的(比如轻量级工具类)。

DI 容器天然支持这些生命周期管理。你不用自己写单例模式,不用自己搞线程安全。

一句话总结:DI 让代码更灵活、更可测、更易维护。说白了,就是让你改代码的时候少掉头发。

1.5 依赖注入的优缺点分析

任何技术都有两面性。DI 也不是银弹。我客观说说它的优缺点。

优点

优点 说明
降低耦合度 类之间通过接口交互,不依赖具体实现
提高可测试性 可以轻松注入 Mock 对象进行单元测试
增强可维护性 替换实现只需改注册代码,不用改业务逻辑
生命周期管理 容器自动管理对象的创建和销毁
促进面向接口编程 强制开发者思考抽象,而不是具体实现

缺点

缺点 说明
学习曲线 新手理解 IoC 和 DI 需要时间
调试困难 运行时才知道具体注入的是哪个实现,堆栈跟踪变复杂
性能开销 反射和动态代理会带来轻微性能损失(通常可忽略)
过度设计风险 小项目硬上 DI,反而增加复杂度
配置复杂 依赖关系复杂时,注册代码可能变得冗长

避坑指南:我曾经在一个只有三个控制台程序的项目里强行上了 DI 全套。结果代码量翻了一倍,维护起来反而更麻烦。后来我学乖了——DI 是好东西,但别滥用。小项目、原型阶段,直接 new 反而更高效。

1.6 什么时候该用,什么时候不该用?

我个人的经验是:

  • 该用:项目规模中等以上、需要单元测试、可能替换实现、多人协作
  • 慎用:简单工具类、一次性脚本、性能极端敏感的场景、原型验证阶段

嗯,这里要注意一点:DI 不是非黑即白的。你可以只在需要的地方用 DI,其他地方保持简单。别为了用 DI 而用 DI。

1.7 小结

这一章我们聊了:

  • 依赖注入是什么——让别人把依赖送过来
  • 控制反转是什么——控制权从类内部转移到外部容器
  • 为什么需要 DI——解耦合、好测试、易维护
  • DI 的优缺点——灵活但有代价,别滥用

下一章,我会带你看看 .NET 里 DI 容器的具体用法。包括怎么注册、怎么解析、三种生命周期有什么区别。到时候我会拿我踩过的坑当反面教材,保证让你印象深刻。

课后思考:你现在写的代码里,有没有那种「new 来 new 去」的地方?试着想想,如果改成依赖注入,会带来什么好处?又会引入什么麻烦?