Node.js服务端开发教程 (四):依赖注入基础篇

最新推荐:《Vue3.0抢先学》系列学习教程

"下单成功!您的专车老司机正在赶来接驾的路上,请稍等几分钟......"

现代的服务业真是越做越到位了,我们只要提供出我们的需求,就会有人主动来提供服务,针对性的解决我们的问题。就如上面的打车服务一样,我们不再需要像以前一样,在寒风凛冽的马路上、大雨瓢泼的黑夜里,哆哆嗦嗦的招手拦车,一辆辆的问司机走不走,司机大哥忙着要下班,不走;司机大哥还没吃饭,不走;司机大哥心情不好,不走;路程太近,不走;路程太远,不走......我们只需要在温暖的房间里向打车软件系统告知我们的行程信息、偏好信息等,就会有愿意服务我们的司机上门为我们服务。

将上面的例子引申到编程领域,就是我们今天要聊到的“依赖注入”这个概念了。依赖注入,洋气一点,叫做 Dependency Injection(DI);从另一个角度来描述,也可以叫做“控制反转”,Inversion of Control(IoC)。它不是指代某种技术或框架,而是一种编程的设计思想。著名的好莱坞法则能很好的表达这种思想:

“别来找我们,我们去找你”

在软件开发领域,将依赖注入应用的最为成熟、知名度最高的框架,非Java的 Spring Framework 莫属了。

再次回到打车的例子,我们可以发现这样的关系:

  • 传统的打车方式:消费者需要主动寻找和匹配可以进行服务的提供者
  • 现代的打车方式:消费者的控制权被弱化,转而由服务的提供者来主动对消费者进行匹配

对比这两种情况,消费者和服务者之间的关系在后者的情况下被反转了。而在真实生活中的类似情况下,采用这种模式的服务质量和效率会得到较大幅度的提升。

下面,我们来通过一些简单的代码示例,来了解为什么需要在软件编程中引入控制反转这个概念。

class Car {
  constructor(private readonly name: string) { }
  
  start() {
    console.log(`Your ${this.name} is start running!`);
  }
}

class User {
  constructor(private readonly name: string) { }
  
  drive() {
    let car = new Car('不知名品牌');
    car.start();
  }
}

const kevin = new User('一斤代码');
kevin.drive();

上面这段代码,描述的是一个充满智慧和动手能力的劳动者一斤代码同学想要开车的场景:要开车,先造车!这个劳动者每次要开车,他都必须先造一辆车,然后才能开它。这也太不经济和环保了吧!于是,一斤代码同学开始思考起来另一种方案:先造一辆车放那儿,然后在需要开的时候去开它:

class Car {
  constructor(private readonly name: string) { }

  start() {
    console.log(`Your ${this.name} is start running!`);
  }
}

class User {
  constructor(
    private readonly name: string,
    private readonly car: Car
) { }

  drive() {
    this.car.start();
  }
}

const car = new Car('保时捷911');

const kevin = new User('一斤代码', car);
kevin.drive();

const tom = new User('十斤代码', car);
tom.drive();

这次终于不用每次开车就造车了,事先就造好一辆(保时捷的,还是911),它可以被共享和复用:一斤代码同学可以用这辆车,他的好友十斤代码同学也可以用!经济又环保。

但在上面的代码场景中,还有有些不理想的地方,就是我们还能看到造车的环节。难道我们逃不出要开车前非得看一下怎么造车的宿命吗?!这种变态设定,一定不会允许它长期存在的。

于是“代码社会”率先进化到了理想中的完美形态:由一个社会资源的总分配者来为大家按需分派资源。总分配者事先在资源池里生产好车辆,等有人想开车的时候,就主动把符合要求的车分配给他们,送货上门!使用者不必再关心这些资源的生产细节,只要关心怎么使用就行了。

这样一个资源的总分配者,就是IoC框架的核心:容器。基于IoC框架的应用程序开发中,我们编写的代码都依赖于这个容器,容器管理着代码中各个对象间的关联关系,为它们注入需要的外部资源。对比以前传统方式,创建对象的主动权和创建时机由完全是自己来把控,变成了将这种权力转移到了第三方(即容器)。

NestJS中依赖注入的初步使用

NestJS作为一个实现了依赖注入功能的框架,同样拥有上述所说的容器的特性。其实,容器最适合管理的资源是那些具有高可复用性的内容,如:配置信息、全局常量、可复用的业务逻辑组件、以及工具类等等。

我们来通过编写一些简单代码,尝试把上面代码例子中的汽车作为可复用资源放入NestJS的资源容器中。请打开你在本教程第一篇中新建的项目代码吧。

步骤一:将Car类使用 @Injectable 装饰器,声明成可被容器进行注入的资源

// car.ts

import { Injectable } from "@nestjs/common";

@Injectable()
export class Car {
  constructor(private readonly name: string) { }

  start() {
    console.log(`Your ${this.name} is start running!`);
  }
}

步骤二:在模块配置中,添加Car类的对象提供器

// app.module.ts

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

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    // 配置一辆保时捷
    {
      provide: Car,
      useFactory: () => {
        return new Car('保时捷911');
      }
    }
  ],
})
export class AppModule { }

步骤三:通过构造函数,注入需要使用Car的类中

// app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { Car } from './car';

@Controller()
export class AppController {
  constructor(
    // 自动注入Car对象
    private readonly car: Car  
  ) { }
  
  @Get('test/car')
  testCar(): string {
    // 控制台打印car的内容
    console.log(this.car);
    // 调用car对象的方法
    this.car.start();
    return 'test done!';
  }
}

然后,将NestJS程序启动起来,访问浏览器:

http://localhost:3000/test/car

成功运行的话可以在命令行上看到如下的信息,说明我们的保时捷已经成功被注入到AppController对象实例中,并被成功使用:

一个小探索

接着,给大家多做些尝试的机会,请再多配置一辆宝马车:


// app.module.ts

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

@Module({
  imports: [],
  controllers: [AppController],
  providers: [
    AppService,
    // 配置一辆保时捷
    {
      provide: 'PORSCHE',
      useFactory: () => {
        return new Car('保时捷911');
      }
    },
    // 配置一辆宝马i8
    {
      provide: 'BMW',
      useFactory: () => {
        return new Car('宝马i8');
      }
    }
  ],
})
export class AppModule { }

然后修改 AppController 的构造函数部分,使用 @Inject 装饰器并将它的参数换成PORSCHE或BMW,看看最后运行的结果有什么不一样吧!

constructor(
    @Inject('BMW')
    private readonly car: Car
) { }

总结

依赖注入是软件开发中,一种可增强代码的复用性、解耦合的有效设计思想,对开发中大型软件、促进团队成员间分工协作有着非常大的好处。希望这些优秀的工具能开拓大家的思路,也能帮助大家在实际的开发过程中解决遇到的相应问题。

让我们在后面的章节中,花更多的时间去继续探讨依赖注入相关的知识。

关注首发公众号:默碟

推荐阅读更多精彩内容