DI依赖注入与IOC控制反转

2023/7/12 nest

# 前言

上篇文章我们学习了如何使用nest-cli来快速生成一个NestJS后端项目,当我们打开编辑器查看代码时,会发现整个代码风格有点类似JAVA的spring框架,并且你会发现一些service类在controller控制器的constructor中注入后,可以不需要手动new就可以直接使用该类对应的实例方法。

比如:

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/hello')
  get(): string {
    return this.appService.getHello();
  }
}

这其实就是Nest依赖注入与控制反转,目的主要是方便代码之间的解耦从而减少维护成本。

# 什么是依赖注入(DI)与控制反转(IOC)

这两个概念不要搞混了,IOC其实是面向对象编程中的一种设计模式,而DI则是为了实现IOC的一种技术。

# 传统耦合代码

比如,我们有两个类,它们之间存在耦合关系,我们一般会这样写:

// A.ts
class A {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

// B.ts
class B {
  age: number
  name: A
  constructor(age: number) {
    this.age = age
    this.name = new A('南玖')
  }
}

// main.ts
const b = new B(18)
console.log(b)

当我们遇到类与类之间存在依赖关系时,一般会直接在类的内部创建依赖对象,这样就会导致各个类之间形成耦合,并且这种关系会随着依赖关系越来越复杂从而耦合度也会越来越高,最终造成代码的难以维护。

# 简易版IOC

为了解决上面代码带来的耦合性问题,我们可以使用IOC容器来进行管理

// container.ts
export class Container {
  modules = new Map()
  
  // 注册实例
  provide(key: string, clazz: any, argvs: Array<any>) {
    this.modules.set(key, {clazz, argvs})
  }
  // 获取实例
  get(key: string) {
    const {clazz, argvs} = this.modules.get(key)
    return Reflect.construct(clazz, argvs)
  }
}

这里的Reflect.construct是为了帮我们实例化一个对象。

以上就是容器化思路,统一管理,可以实现类与类之间的解耦。

# Nest JS的IOC与DI

依赖注入是一种控制反转IOC(inversion of control)技术,就是你可以把对象或依赖的实例化交给IOC容器去处理,在NestJS中这个容器就是NestJS的运行时系统。当需要一个对象实例的时候,我们不需要自己手动new xxxClass(),只需要在合适的地方对类进行注册,在需要用到的地方直接注入,容器将为我们完成new的动作

Nest中使用依赖注入一般有以下三步:

# 声明定义

使用@Injectable装饰器来声明一个类,它表示该类可以由NestIOC容器管理

// app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello nanjiu';
  }
}

# 声明在什么地方使用

这是依赖注入的地方,一般是在类的构造函数constructor中注入,只有完成注入后才可以使用

// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get('/hello')
  get(): string {
    return this.appService.getHello();
  }
}

官方把appService称为tokenNestJS会根据这个token在容器中找到第1步中声明的类(这个对应关系将在第三步中进行关联注册),从而提供对应的实例,这里的实例全局唯一,只有1个!在第一次需要该实例的时候,Nestnew一个出来,而后会缓存起来,后序如果其它地方也注入了这个依赖,那Nest会从缓存中拿到之前new出来的实例供大家使用。

# 建立注入依赖与容器中类的联系

依赖注入后还需要在Module中进行关联

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Nest会根据所有注入的依赖关系生成一个依赖关系图,就有点类似我们使用import引入各个模块时也会生成一个复杂的依赖关系图。这里AppController中依赖了AppService,如果AppService中还依赖其它东西也会一并放到Nest构建的依赖关系图中,Nest会从下到上按照依赖顺序构建出一整张依赖关系图保证所有的依赖关系正常运作。