3、Nest.js 中的依赖注入与提供者
什么是依赖注入?
依赖注入(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 的实现, 我们只需要专注于修改这个提供者,然后对这个提供者进行单元测试。
如果能够实现标准的面向接口编程一切将会变得更好,我们的程序耦合度将会更低。