在大型项目的管理中,控制反转的思想是非常重要的。它可以帮助我们解耦代码,提高代码的可维护性。同时避免了不必要的重复实例化,降低内存泄漏的可能性。
而在 JS/TS 技术栈中,我们通常会使用依赖注入框架来帮助我们管理服务。这其中最佳的选择当然是 Angular 这种大而全的大型工程开发框架。而对于使用了其他 UI 框架的项目来说,我们同样可以额外引入一个轻量化的依赖注入框架。而 InversifyJS 就是其中的佼佼者。我们可以通过使用它,来见微知著地了解依赖注入的原理与设计哲学。
但最近在使用 Inversify 进行项目重构时,遇到了一个问题:众所周知依赖注入框架天生适合管理单例服务。它的设计哲学是 Everything as Service
。但是在某些场景下,单例模式并不能解决一切问题,我们同样需要进行多实例的管理。那么我们该如何解决这个问题呢?
这并不是 Inversify 框架的问题,而其实是一个依赖注入框架下常见的设计疑惑,但是网上对此的解析资料却很少。
我看了很多使用了 InversifyJS 的项目,他们对此的方式就是直接在需要处实例化,不将其注册到容器中。这实际上是没有真正理解到依赖注入框架的内核。这样做的好处是简单,但是有很多弊端。由于我们无法在容器中统一管理这些实例,那么这些服务的生命周期将不受控制,在 dispose 时无法在容器中统一销毁这些实例。与不引入依赖注入框架一样,这样同样会带来内存泄漏的可能性。
那么该如何正确地处理这种情况呢?
构造器注入
一个最简便的改造方式是,我们将类的构造函数绑定到容器中。需要的时候从容器中获取类的构造器,再进行实例化。这样我们就可以在容器中统一管理这些实例了。
// 将 InstanceClass 的构造函数绑定到容器中
container
.bind<interfaces.Newable<InstanceClass>>("Newable<InstanceClass>")
.toConstructor<InstanceClass>(InstanceClass);
// 获取构造器
public constructor(
@inject("Newable<InstanceClass>") InstanceClass: Newable<InstanceClass>,
) {
this.instance1 = new InstanceClass();
this.instance2 = new InstanceClass();
}
实例会跟随类的生命周期而存在,且该类能纳入容器中进行管理。但是这样做,实际上仍然无法在容器中统一管理这些实例的生命周期。如果我们需要在 dispose 时销毁这些实例,那么我们需要在类中手动实现 dispose 方法,并在 dispose 时手动销毁这些实例。
这样改造的好处是简单,但是很多时候并不是一个最优解,因为我们希望该实例本身能在注入框架的管理下,避免我们去手动的控制与销毁。
工厂注入
依赖注入框架天生不太好管理多实例的服务,但是如果利用工