掘金 后端 ( ) • 2024-04-01 16:17

1. 概念

IoC(Inversion of Control)和 DI(Dependency Injection)是面向对象编程中的两个相关概念,它们主要用于解决程序中的依赖管理和解耦问题。

2. IOC (控制反转)

  • 控制反转是一种设计原则,它涉及到对象创建和依赖管理的方式。在传统的编程方式中,对象通常自行创建或查找它们依赖的对象。而在IoC模式下,这种创建和查找依赖对象的责任从应用程序代码中转移到了外部的第三方(通常是框架或容器),即所谓的“控制权反转”给了容器。
  • 核心思想:对象不再直接控制其依赖对象的创建和生命周期管理,而是由外部容器负责这些工作。容器知道哪些对象需要哪些依赖,并负责在适当的时候将依赖注入到需要它们的对象中。通过这种方式,对象之间的依赖关系变得更加透明,降低了耦合度,提高了代码的可重用性和可测试性。

3. DI

  • 依赖注入是实现IoC的一种具体设计模式或技术手段,是IoC原则的常见实现方式之一。
  • 概念:依赖注入是指在对象实例化或运行时,由外部容器或框架负责将依赖对象(服务、组件、模块等)注入到需要它们的对象(客户端、消费者)中。注入可以是通过构造函数、属性(setter方法)或接口方法等方式进行。
  • 目的:通过依赖注入,对象不再关心依赖对象的具体实现细节和生命周期管理,只需声明它需要哪些依赖(接口或抽象类型),由外部容器负责提供正确的依赖实例。这样,对象间的依赖关系变得松散,容易更改,有利于代码的解耦和测试。

4. 案列

class A {
    name: string
    constructor(name: string) {
        this.name = name
    }
}
 
 
class B {
    age:number
    entity:A
    constructor (age:number) {
        this.age = age;
        this.entity = new A('xt')
    }
}
 
const c = new B(18)
 
c.entity.name

我们可以看到,B 中代码的实现是需要依赖 A 的,两者的代码耦合度非常高。当两者之间的业务逻辑复杂程度增加的情况下,维护成本与代码可读性都会随着增加,并且很难再多引入额外的模块进行功能拓展


class A {
    name: string
    constructor(name: string) {
        this.name = name
    }
}
 
 
class C {
    name: string
    constructor(name: string) {
        this.name = name
    }
}
//中间件用于解耦
class Container {
    modeuls: any
    constructor() {
        this.modeuls = {}
    }
    provide(key: string, modeuls: any) {
        this.modeuls[key] = modeuls
    }
    get(key) {
        return this.modeuls[key]
    }
}
 
const mo = new Container()
mo.provide('a', new A('xt1'))
mo.provide('c', new C('xt2'))
 
class B {
    a: any
    c: any
    constructor(container: Container) {
        this.a = container.get('a')
        this.c = container.get('c')
    }
}
 
new B(mo)

其实就是写了一个中间件,来收集依赖,主要是为了解耦,减少维护成本

5. nest中如何使用的IoC

5.1. 创建项目

nest new nest-ioc

pnpm start

浏览器访问 http://localhost:3000 就可以看到 nest 服务返回的 hello world

5.2. 它是怎么创建对象的

(红色报错的地方是eslint代码检测问题,我后面再教大家如何配置,现在先忽略)

我们可以看到在app.service,他声明了 @Injectable,代表这个 class 可注入,那么 nest 就会把它的对象放到 IOC 容器里

当然你还可以写成这样

(前者是构造器注入,后者是属性注入)

app.controller声明了 @Controller,代表这个 class 可以被注入,nest 也会把它放到 IoC 容器里。

app.controller 的构造器参数依赖了 app.service。

为什么 Controller 是单独的装饰器?

因为 Service 是可以被注入也是可以注入到别的对象的,所以用 @Injectable 声明。而 Controller 只需要被注入,所以 nest 单独给它加了 @Controller 的装饰器。

通过 @Module 声明模块,其中 controllers 是控制器,只能被注入

providers 里可以被注入,也可以注入别的对象,比如这里的 AppService。

当执行start的时候 main.ts的内容就会被执行,那么 nest 就会从 AppModule 开始解析 class 上通过装饰器声明的依赖信息,自动创建和组装对象

5.3. 模块机制

nest g module user

可以看到 他帮我们生成了代码,并自动的往app.module 里自动 imports 了这个模块

我们在来生成一个service

nest g service user

可以看到 他帮我们生成了代码,并自动添加到 user.module 的 providers 中

我们修改一下user.service的代码并在user.module中exports

在之前我们说到过,app.module中是不是已经引用了user.module了,那么他现在他就可以注入exports 的 UserService 了