【NestJS系列】DI依賴注入與IOC控制反轉

2023-07-17 12:01:01

前言

上篇文章我們學習瞭如何使用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會從下到上按照依賴順序構建出一整張依賴關係圖保證所有的依賴關係正常運作。