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

上一篇:2、Nest.js 创建基本的REST控制器
下一篇:4、Nest.js 中的模块化设计

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341