控制反转与依赖注入
控制反转 (IoC)
控制反转 (Inversion of Control,IoC) 是一种软件设计思想,旨在将对象的创建和管理交给外部容器 (如 IoC 容器),而不是在代码中硬编码。通过 IoC,对象之间的依赖关系由容器管理,而不是在代码中显式地创建和配置。可以降低计算机代码之间的耦合度。
在 Nest 中,IoC 容器负责管理对象的生命周期和依赖关系。
依赖注入 (DI)
依赖注入 (Dependency Injection,DI) 是 IoC 的一种实现方式,通过将依赖项 (即对象之间的依赖关系) 注入到目标对象中,实现控制反转。
在 Nest 中,依赖注入主要是通过装饰器 (Decorators) 实现的,通过在目标对象上添加装饰器,将依赖项注入到目标对象中。
两者关系
控制反转是一种设计思想 (设计模式),而依赖注入是控制反转的一种实现方式。
依赖注入的实现
首先我们来看一下传统的开发方式,在传统的开发模式中,对象之间的依赖关系由代码中显式地创建和配置。
// 假设这是我们的 CPU 和 GPU 类
class IntelCPU {
process(): void {
console.log('Intel CPU 运行中...');
}
}
class NvidiaGPU {
render(): void {
console.log('Nvidia GPU 渲染中...');
}
}
interface CPU {
process(): void;
}
interface GPU {
render(): void;
}
// 这是我们的 Computer 类,它直接创建 CPU 和 GPU 的实例
class Computer {
cpu: CPU;
gpu: GPU;
constructor() {
this.cpu = new IntelCPU(); // 硬编码依赖
this.gpu = new NvidiaGPU(); // 硬编码依赖
}
run(): void {
this.cpu.process();
this.gpu.render();
}
}
// 使用 Computer 类
const computer = new Computer();
computer.run();
在这个例子中,Computer 类在构造函数中直接创建了 IntelCPU 和 NvidiaGPU 的实例。这种方式的缺点是,如果我们需要更换 CPU 或 GPU 的实现,我们需要修改 Computer 类的代码。
在依赖注入中,组件不直接创建依赖项,而是在创建时通过构造函数、属性或方法参数接收依赖项。
现在我们来看一下使用依赖注入后的代码。
// CPU 和 GPU 接口
interface CPU {
process(): void;
}
interface GPU {
render(): void;
}
// 实现类
class IntelCPU implements CPU {
process(): void {
console.log('Intel CPU 运行中...');
}
}
class NvidiaGPU implements GPU {
render(): void {
console.log('Nvidia GPU 渲染中...');
}
}
// Computer 类,使用依赖注入
class Computer {
cpu: CPU;
gpu: GPU;
constructor(cpu: CPU, gpu: GPU) {
this.cpu = cpu;
this.gpu = gpu;
}
run(): void {
this.cpu.process();
this.gpu.render();
}
}
// 创建 Computer 实例时,我们传递 CPU 和 GPU 的实例
const intelCpu = new IntelCPU();
const nvidiaGpu = new NvidiaGPU();
const computer = new Computer(intelCpu, nvidiaGpu);
computer.run();
在这个例子中,Computer 类通过构造函数接收 CPU 和 GPU 的实例。这样,我们可以根据需要传递不同的实现,而不需要修改 Computer 类的代码。
Nest 中依赖注入的实现
在 Nest 中,依赖注入 (DI) 是通过装饰器和 IoC 容器实现的。以下是一个简单的例子,展示了如何在 Nest 应用程序中使用依赖注入:
我们定义一个服务 (Service) 和一个控制器 (Controllers),然后使用 Nest 的装饰器来标记它们。
首先,我们创建一个服务类,使用 @Injectable()
装饰器来标记它为可注入的服务。
// src/app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
然后,我们创建一个控制器类,它将依赖于 AppService
。在控制器的构造函数中,我们通过类型注解来声明依赖,并让 Nest 的 IoC 容器自动注入它。
// src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
此时,AppService
作为一个提供者 (Providers),需要在模块中进行注册。
// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
最后,我们启动 Nest 应用程序,Nest 会自动处理依赖注入。
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
在这个例子中,当 AppController
被创建时,NestJS 的 IoC 容器会查找 AppService
的提供者,并将其实例注入到 AppController
的构造函数中。这样,AppController
就可以使用 AppService
的方法,而不需要自己创建 AppService
的实例。