依赖注入 — 从参数里接过来
一句话总结
依赖注入并不是什么艰深概念,本质上就是通过参数接收依赖。不直接引用全局对象,而是从外部传入,就能在测试时换成别的实现。
为什么需要它——并不只是为了测试
如果把 DI 理解成“为了测试才使用的技术”,就偏离了它的真正目的。真正的理由是:在代码中明确标出可以替换实现的位置。
假设要把存储层从 PostgreSQL 改成前置 Redis 缓存。如果处理程序直接调用 db.query(...),就必须修改所有处理程序;如果存储层是通过参数接收的,只需修改负责注入依赖的一侧。可测试性只是这种特性的副产品。
手工实现时是这样
export function createApp({ store }) {
return async function handle(req) { /* store 를 쓴다 */ };
}
框架所做的,不过是自动找到这个 { store } 并把它注入进去。Nest 的 provider、Spring 的 Bean 都是在自动化这件事。如果不知道自动化隐藏了什么,注入失败时也就不知道该检查哪里。
在实际项目中
面试被问到“为什么使用 DI”时,最常见的回答是“因为更容易测试”。这并没有错,但只答对了一半。真正的目标是反转依赖方向,让上层模块不被下层模块的具体实现绑住;便于测试只是由此产生的结果。
而在实际工作中,DI 总是在同一个地方失效:为了“方便”而直接 import 某个全局单例的那一刻。从那以后,这个模块就再也无法自由替换了。