八、IOC容器示例和SOLID原则

本节我们介绍下一些术语,了解我们后续要开发的内容

IOC容器

IOC指的是控制反转(Inversion Of Control),是一种设计原则,用于减少代码之间的耦合。

  • 在传统的编程方式中,程序直接控制所依赖的对象的创建和管理

  • 使用IOC后,对象的创建和管理权被反转给了容器,程序不再主动负责创建对象,而是被动的接收容器注入的对象

DI是依赖注入(DependencyInjection),是实现IOC的一种手段,通过DI,我们将类的依赖项注入到类中,而不是在类里边实例化这些依赖。

来一个简单的示例演示一下IOC容器:

  • EngineCar是为了演示IOC容器的,因为我们用到了design:paramtypes这个元数据来获取构造函数的参数,所以需要给这两个类加装饰器,即便装饰器什么都不做,如果没有装饰器的话,就不会有元数据生成了
  • DIContainer是我们的容器,只做了两件事,服务的注册和解析。我们把所有的服务注册进去,然后通过resolve来取某一个服务的时候,会将其依赖的其他服务自动实例化注入进去
  • 需要注意的是,目前只支持了服务的依赖注入,如果你需要一些其他值,比如constructor(age: number)这种,需要与nestjs一样,通过useValue, useFactory等方式来实现,这个后边再说,这里不做实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import 'reflect-metadata'

// Engine和Car两个类,用来演示IOC容器
// 要想让一个类发射design:xxxtype,需要给它添加一个装饰器
// 这里与nestjs一样,我们给provider添加一个@Injectable装饰器
@Injectable
class Engine {
start() {
console.log('Engine started')
}
}
@Injectable
class Car {
constructor(private engine: Engine) {}
drive() {
this.engine.start()
console.log('Car is running')
}
}
// @Injectable装饰器,我们只是为了让类能发射design:xxxtype,也就是元数据生成,所以装饰器内容可以为空
function Injectable(target) {}

// 定义一个依赖注入的容器类
class DIContainer {
// 存储所有服务的Map对象
private services = new Map<string, any>()

// 注册服务
register<T>(name: string, service: new (...args: any[]) => T) {
// 把类的名字和构造函数存到Map中
this.services.set(name, service)
}

// 解析/使用服务 - 根据服务名称返回服务实例
resolve<T>(name: string): T {
// 获取实现类
const Service = this.services.get(name)
// 通过design:paramtypes找到Service的依赖项 - 即该类的构造函数的参数
const dependencies = Reflect.getMetadata('design:paramtypes', Service) ?? []
// 获取所有依赖项的实例 - 递归调用resolve,把所有的依赖都拿到
const injections = dependencies.map(dep => this.resolve(dep.name))
// 将依赖项注入,创建并返回实现类的实例
return new Service(...injections)
}
}

// 创建一个容器实例
const container = new DIContainer()
// 注册服务
container.register<Engine>('Engine', Engine)
container.register<Car>('Car', Car)
// 解析服务
const car = container.resolve<Car>('Car')
car.drive()

SOLID原则

SOLID是一个软件设计的指导原则,每个字母代表一个设计原则

  • 单一指责原则,要求一个类应该只有一个引起变化的原因,换句话说就是一个类应该只有一个职责
  • 开闭原则,要求实体应该对扩展开放,对修改关闭
  • 里氏替换原则,要求子类必须能够替换掉它们的基类,这意味着字类能在任何地方替换掉其父类而不会导致异常
  • 接口隔离原则,要求类之间的依赖关系应该建立在最小的接口上,也就是说,不应该强迫一个类依赖于它不使用的方法
  • 依赖倒置原则,要求高层模块不应该依赖低层模块,二者都应该依赖抽象,也就是接口或抽象类。依赖关系应该通过抽象实现,而不是通过具体来实现
作者

胡兆磊

发布于

2025-04-08

更新于

2025-04-21

许可协议