IOC,全称为 Inversion Of Control,即 控制反转。
控制反转是面向对象编程中的一种设计原则,作用是降低各个模块之间的耦合度。
控制反转是思想,不是具体实现。
假如 Class A 需要依赖 Class B,我们一般在 A 的构造函数中实例化 B,像这样:
class A {
constructor() {
this.b = new B();
}
// ...
}
这导致了耦合,A 对 B 的依赖,是写在 A 的实现中的。如果你要把 B 换成一个加强版的 BPlus,你就要改 A 的实现。
这时候,我们可以用控制反转。
“控制反转” 这个词怎么理解?就是将原本需要程序员手动控制的程序流程,改成通过框架来控制,从原来的程序员手工控制,改为框架控制。
DI,英文全称 Dependency Injection,即依赖注入。
依赖注入是控制反转的一种常见实现。
依赖注入这词听起来高大上,很有噱头,实际上实现非常简单,就是将依赖的 Class 先在外面实例化好,再注入到需要它的 Class 中。
像前面的 A 和 B 的依赖关系,我们可以改成:
class A {
constructor(b) {
this.b = b;
}
// ...
}
const b = new B(); // 在外部实例化 B
const a = new A(b); // 依赖注入
上面是通过构造函数来注入实例对象。我们也可以额外写一个 setB 方法来注入:
class A {
constructor() {}
setB(b) {
this.b = b;
}
// ...
}
const b = new B(); // 在外部实例化 B
const a = new A();
a.setB(b); // 依赖注入
使用了依赖注入的技巧后,A 和 B 就解耦了,B 就可以很方便地做替换,如:
const bPlus = new BPlus(); // 在外部实例化 B
const a = new A(bPlus); // 依赖注入
如果你用一些框架,它们可以把依赖注入过程做得更优雅,比如 Nestjs。
import { UserService } from './user.service';
@Controller('user')
export class UserController {
// 通过类型,判断要进行注入哪个类
constructor(private readonly userService: UserService) {}
@Get()
findAll() {
// 这里我们用到了 this.userService
// 我们代码里没写注入逻辑,但 Nestjs 帮我们注入了
return this.userService.findAll();
}
}
在 Nestjs 的 Controller 类中,我们只要在构造函数中声明该类,Nestjs 就能自动扫描注册的依赖列表,从中找出正确的类,并帮你实例化并注入,完全不需要你手动操作。
Nestjs 能做到这点,是利用了 TypeScript 的装饰器和 Reflect.metadata 的能力。