什么是依賴注入?
依賴注入(Dependency Injection,簡稱DI) 是實現 控制反轉(Inversion of Control,縮寫為IoC) 的一種常見方式。
那什么是控制反轉呢?
這里摘抄一下百科上的解釋,控制反轉,是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度。通過控制反轉,對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象。
現在說人話:
假設你是一個想開公司的富二代,開公司首先需要一間辦公室。那么你不用自己去買,你只需要在你的清單列表上寫上辦公室這么一項,OK! 你老爸已經派人給你安排好了辦公室,這間辦公室長什么樣?多大?在哪里?是租的?還是買的?你根本不知道,你也不需要知道。 現在你又在清單上寫了需要80臺辦公電腦,你老爸又給你安排好了80臺 Alienware, 你自己并不需要關心這些電腦是什么配置,買什么樣的CPU性價比更高,只要他們是能辦公的電腦就行了。
那么你的老爸就是所謂的 IoC 容器,你在編寫 Company 這個 class 的時候,你內部用到的 Office 、Computers
對象不需要你自己導入和實例化,你只需要在 Company 這個類的 Constructor (構造函數) 中聲明你需要的對象,IoC 容器會幫你把所依賴的對象實例注入進去。
代碼可能類似于下面這樣:
import { IOffice } from './interfaces/IOffice.interface';
import { IComputers } from './interfaces/IComputers.interface';
export class Company {
constructor(
private readonly office: IOffice,
private readonly computers: IComputers[]
) {}
}
這里有必要解釋一下 readonly 關鍵字,它表示被修飾的對象只能在聲明的時候或構造函數中初始化。
Nest 中的依賴注入
Nest 就是建立在依賴注入這種設計模式之上的,所以它在框架內部封裝了一個IoC容器來管理所有的依賴關系。
在 MVC 設計模式中, Controller 只負責對請求的分發,并不處理實際的業務邏輯。所以我們的 UsersController 不應該自己處理對 用戶 的增、刪、改、查。 需要把這些工作交給 Service 層。
按照標準的 面向接口編程 我們在 UserController 構造函數里應該聲明實現 IUserService 接口的 UserService 對象,如下:
src/users/interfaces/user-service.interface.ts
import { User } from './user.interface';
export interface IUserService {
findAll(): Promise<User[]>;
findOne(id: number): Promise<User>;
create();
edit();
remove();
}
src/users/users.controller.ts
import { Controller, Param, Get, Post, Delete, Put } from '@nestjs/common';
import { User } from './interfaces/user.interface';
import { IUsersService } from './interfaces/user-service.interface';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: IUsersService) {
}
@Get()
async findAll(): Promise<User[]> {
return await this.usersService.findAll();
}
@Get(':id')
async findOne(@Param() params): Promise<User> {
return await this.usersService.findOne(params.id);
}
@Post()
async create() {
return await this.usersService.create();
}
@Put()
async edit() {
return await this.usersService.edit();
}
@Delete()
async remove() {
return await this.usersService.remove();
}
}
但是 TypeScript 只是一門轉譯語言, 最終生成并執行的是 JavaScript, 所以 TypeScript 的 Interface 會在轉譯成 Javascript 后去除,Nest 無法引用這些接口,在Nest中我們需要為IoC容器指定 提供者。
提供者(Provider)
一個提供者就是一個可以被IoC容器注入依賴關系的對象。
現在我們來新建一個提供者 UserService:
$ nest g s users/services/users
CREATE /src/users/services/users.service.spec.ts (449 bytes)
CREATE /src/users/services/users.service.ts (89 bytes)
UPDATE /src/app.module.ts (858 bytes)
生成的代碼如下:
src/users/services/users.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class UsersService { }
在Nest中不僅僅只有 Service 能作為提供者,repository、 factory、 helper 等等,都可以作為提供者,也可以相互依賴。
所謂 提供者 不過就是一個簡單的 JavaScript 類, 然后使用 @Injectable 裝飾器修飾。
現在我們讓 UserService 實現 IUserService 接口,并給框架指定提供者:
src/users/services/user.service.ts
import { Injectable } from '@nestjs/common';
import { User } from '../interfaces/user.interface';
import { IUserService } from '../interfaces/user-service.interface';
@Injectable()
export class UsersService implements IUserService {
async findAll(): Promise<User[]> {
return [];
}
async findOne(id: number): Promise<User> {
return {
id,
name: '小明',
age: 18
};
}
async create() {
}
async edit() {
}
async remove() {
}
}
目前我們在 app.module.ts 中指定:
src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from 'app.controller';
import { AppService } from 'app.service';
import { UsersController } from 'users/users.controller';
import { UsersService } from './users/services/users.service';
@Module({
imports: [],
controllers: [AppController, UsersController],
providers: [AppService, UsersService],
})
export class AppModule { }
在 @Module 裝飾器中我們給 providers 數組新增一個 UsersService 提供者
如果你一直跟著前面的教程做,現在訪問 http://127.0.0.1:3000/users/33,會得到下面這樣預期的結果:
{"id":"33","name":"小明","age":18}
得益于依賴注入設計模式,假設我們現在想要更換 UserService 的實現, 我們只需要專注于修改這個提供者,然后對這個提供者進行單元測試。
如果能夠實現標準的面向接口編程一切將會變得更好,我們的程序耦合度將會更低。