js 中的 控制反转-依赖注入

1,031 阅读3分钟

启蒙

最近在学习 nestjs, 发现了一种比较独特的写法,不需实例化 **class** 就可以在另外一个不相干的 class 中使用

举个例子🌰:

cat.module.ts

cat.module.ts 中同时引入 CatsControllerCatsService

import { CatsController } from './cat.controller';
import { CatsService } from './cat.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

cat.service.ts

cat.service.ts 定义 CatsService 类, 定义方法 find, 方法内输出 find 字符

export class CatsService {
 async find(num: Number){
    console.log("find")
  }
 }

cat.controller.ts

cat.controller.ts 中,我们并没有实例化 CatsService 类,只是用了他的类型,但是却在 findAll 中可以使用 CatsService 类中的方法

import { CatsService } from "./cat.service";
export class CatsController {
    constructor(private readonly catsService: CatsService){}
     findAll(){
         this.catsService.find()
     }
}

原理是什么呢?🧐

这是我们今天要讲的 依赖注入(Depedency Injection)

依赖注入的优势清晰易见

  • 无需实例化
    CatsController 中没有实例 CatsService 即可使用, CatsServiceCatsController 进行了解耦

  • 可以复用
    CatsModule 中的 provider 中可以定义多个 service,仅需在 CatsController 引入即可
    也可以在其他 controller 中使用同一个service

什么是依赖注入

在解释依赖注入之前,我们要先知道一个定义,控制反转 (Inverse of Control),也就是我们经常听到的 IOC

控制反转(Inversion of Control,缩写为IoC)是一种编程思想,它的主要目的是将程序中组件之间的控制关系颠倒过来。
在传统的编程模式中,每个对象都负责创建或获取它所需要的其他对象,而在IoC中,这些对象的创建和管理被转移到一个外部容器中,由容器来管理它们的生命周期并将它们注入到需要使用它们的组件中。
这样可以增强程序的灵活性、可扩展性、可维护性和可测试性。

  • 来自 chatGPT3.5

通俗一点解释在传统编程中,CatsService 需要实例化才能在CatsController中使用,使用了控制反转之后,Module 就作为了一个外部容器,把CatsService注入到CatsController

那么依赖注入是实现控制反转思想的其中一种实现

什么是依赖注入呢?🧐

依赖注入(Dependency Injection) 是一种编程模式,它的主要目的是解耦组件之间的依赖关系。

在依赖注入中,一个对象不再负责创建或获取它所需要的其他对象,而是由外部的容器来负责将这些依赖注入进来。这样可以使得代码更加灵活、可测试和可维护。

一图胜千言

传统模式

依赖注入

代码实现

定义两个基础类,一个 wheel 轮子类,一个Engine发动机类,他们都要被 Car 类所使用

class Wheel{
  constructor(private size){
    this.size = size
  }
  roll(){
    console.log("size",this.size)
  }
}


class Engine{
  constructor(private speed){
    this.speed = speed
  }
  start(){
    console.log("Engine",this.speed)
  }
}

传统模式

我们在 Car 中引用 wheelEngine 类,造成了耦合

class Car {
  private wheel:Wheel
  private engine:Engine
  constructor(private brand){
    this.wheel = new Wheel('80cm')
    this.engine = new Engine('50m/s')
    this.brand = brand
  }
  run(){
    this.wheel.roll()
    this.engine.start()
  }
}

🚀 依赖注入

首先我们要定义 容器类 Container,存储所有引入的类

Container

class Container {

 private classMap:Map<string,Object>;
 
 constructor(){
   this.classMap = new Map()
 }
 set(className,injectClass){
   this.classMap.set(className,injectClass)
 }
 get(className){
   return this.classMap.get(className)
 }
}

修改 Car 这个类,注入 Container 类 💉

class Car {
 private wheel:Wheel
 private engine:Engine
 private brand:string
 // 注入 Container 
 constructor(brand,container: Container){
   this.wheel = container.get("wheel") as Wheel
   this.engine = container.get("engine") as Engine
   this.brand = brand
 }
 run(){
   this.wheel.roll()
   this.engine.start()
 }
}

把 Container 的实例注入到 Car 类

let co = new Container();
let wheel = new Wheel('80cm')
let engine = new Engine('50m/s')
co.set("wheel",wheel)
co.set("engine",engine)

let car = new Car("宝马",co)
car.run()

以后如果要修改 Wheel 的尺寸,或者改变 Engine 的尺寸,无需深入到 Car 类中去

总结

写的比较简单,实现起来也不复杂,其实就是写了一个中间件,来收集依赖,主要是为了解耦,减少维护成本

今天是周五,明天劳动节调休