Skip to content

未来改进方向

本文档记录了参考 InversifyJS、TypeDI、NestJS、Spring 等成熟框架经验,对 @kaokei/di 项目提出的潜在改进思路。每个改进点下方的「作者态度」章节用于记录作者对该改进的看法。

1. 作用域(Scope)扩展

当前只支持单例模式,这在很多场景下是不够的。

  • 瞬态作用域(Transient):每次 get() 都创建新实例,适用于无状态的工具类、策略对象等
  • 请求作用域(Request Scope):在一次"请求"上下文中共享同一实例,适用于 HTTP 请求处理链、事务上下文等。NestJS 的 Scope.REQUEST 就是典型案例
  • 实现思路:可以在 Binding 上增加 inTransientScope() / inRequestScope() 链式调用,内部通过 status 和 cache 策略区分。Request Scope 需要引入一个 Context 对象来追踪当前请求

作者态度

可以考虑增加“瞬态作用域”。不考虑“请求作用域”,因为会增加不必要的“复杂度”。

2. 异步解析支持(Async Resolution)

当前 container.get() 是同步的,但 @PostConstruct 支持异步。这导致一个尴尬的局面:调用者拿到实例后,可能 PostConstruct 还没执行完。

  • 提供 container.getAsync(token): Promise<T> 方法,等待整个初始化链(包括异步 PostConstruct)完成后再返回
  • 参考 InversifyJS v7 的 getAsync 设计,以及 NestJS 中 onModuleInit 的异步等待机制
  • 这样调用者可以确信拿到的实例是"完全就绪"的

作者态度

可以考虑增加getAsync这个API。

3. 多重绑定(Multi-Binding)

当前一个 token 只能绑定一个服务,重复绑定会抛异常。

  • 支持 container.getAll(token): T[],获取同一 token 下的所有绑定实例
  • 典型场景:插件系统、中间件链、事件处理器集合。比如 Spring 中 @Autowired List<Strategy> 就是多重注入
  • 可以考虑引入 bindMultibind().allowMultiple() 来区分单绑定和多绑定

作者态度

暂时不考虑。如果是有限的多实例,可以通过创建多个不同的token来绑定同一个服务,也能达到获取多个实例的效果。

4. 条件绑定 / 命名绑定

  • 支持 container.bind(token).toSelf().whenTargetNamed('mysql') 这样的条件绑定
  • 或者更轻量的方案:支持 Token 带泛型标签,比如 new Token<Database>('mysql-db')new Token<Database>('pg-db') 作为不同的 token
  • 当前其实已经可以通过不同的 Token 实例来区分,但缺少一个更优雅的"同接口多实现"的模式

作者态度

暂时不考虑。可以创建多个token来实现类似效果。

5. Container Module / 模块化注册

当前所有绑定都是手动逐个 container.bind() 的,当项目规模变大时会很繁琐。

  • 参考 InversifyJS 的 ContainerModule,支持将一组相关绑定打包成模块
  • 参考 NestJS 的 @Module 概念,声明式地组织 providers / imports / exports
  • 简单实现:container.load(module) 接受一个回调函数 (bind) => { bind(A).toSelf(); bind(B).toSelf(); }
  • 进阶:支持 container.unload(module) 批量卸载

作者态度

暂时不考虑。

6. 自动绑定(Auto-Binding)

当前必须手动 bind(A).toSelf(),即使 token 就是类本身。

  • 参考 TypeDI 的 Container.get(MyClass) 自动注册行为:如果 token 是一个类且未绑定,自动执行 bind(token).toSelf()
  • 参考 Spring 的组件扫描(Component Scan)思路,通过 @Injectable 装饰器自动收集需要注册的类
  • 可以提供一个 container.autoBindSelf = true 的选项,或者 container.get(token, { autoBind: true })

作者态度

暂时不考虑。更加倾向于明确的绑定关系,虽然自动绑定会比较方便,但会导致绑定关系不够明显。

7. 工厂绑定(Factory Binding)

当前 toDynamicValue 可以实现工厂模式,但语义不够明确。

  • 提供 toFactory() 方法,返回的不是实例而是工厂函数,调用者通过工厂函数按需创建实例
  • 典型场景:需要根据运行时参数创建不同配置的实例
  • 参考 InversifyJS 的 toFactorytoAutoFactory

作者态度

暂时不考虑。

8. 中间件 / 拦截器

  • 参考 InversifyJS 的 applyMiddleware,在 get() 调用链中插入自定义逻辑
  • 典型用途:日志记录、性能监控、权限检查、AOP 切面
  • 比当前的 onActivation 更灵活,因为中间件可以拦截每次 get() 调用,而不仅仅是首次激活

作者态度

暂时不考虑。

9. 容器快照(Container Snapshot)

  • 参考 InversifyJS 的 container.snapshot() / container.restore()
  • 在测试场景中非常有用:测试前保存容器状态,测试后恢复,避免测试间的状态污染
  • 实现思路:序列化 _bindings Map 的状态,restore 时恢复

作者态度

暂时不考虑。

10. 类型安全增强

当前的类型推导已经不错,但还有提升空间:

  • container.get() 在使用 Token<T> 时能正确推导类型,但 toDynamicValue 的返回类型校验可以更严格
  • 考虑引入 StrictContainer 模式,在编译期检查所有 token 是否已绑定
  • 参考 TypeDI 的类型推导方式,或者 typed-inject 的编译期 DI 检查

作者态度

暂时不考虑。

11. 调试与诊断工具

  • 提供 container.dump()container.inspect() 方法,输出当前容器的绑定关系图
  • 提供依赖关系可视化:哪些服务依赖哪些服务,形成依赖图
  • 循环依赖的错误信息可以更友好,展示完整的依赖链路径
  • 参考 NestJS 的 DependenciesScanner 和 Angular 的依赖注入调试工具

作者态度

暂时不考虑。

12. 事件系统

当前只有 onActivation / onDeactivation 两个生命周期钩子。

  • 扩展为更完整的事件系统:onBindonUnbindonResolve(每次 get 时)、onChildCreatedonDestroy
  • 支持多个监听器(当前 Container 级别只支持一个 handler)
  • 参考 Spring 的 ApplicationEvent 机制

作者态度

暂时不考虑。

13. 构造函数参数注入的替代方案

Stage 3 装饰器不支持参数装饰器,但可以考虑其他方式:

  • 参考 Angular 的 inject() 函数式 API:在构造函数体内调用 this.service = inject(MyService) 来获取依赖
  • 这需要一个"当前解析上下文"的概念(类似 Angular 的 injection context),在实例化过程中维护一个全局/线程局部的上下文栈
  • 好处是不需要参数装饰器,也能实现构造函数内的依赖获取

作者态度

暂时不考虑。本项目只实现了属性注入功能,所以不存在循环依赖问题,并且降低了项目复杂度,所以暂时不考虑构造函数参数注入,除非将来遇到了属性注入不能解决的问题。

14. 延迟初始化 / 按需加载

  • 当前 LazyInject 是属性级别的延迟注入,但缺少 binding 级别的延迟加载
  • 支持 bind(token).toLazyClass(() => import('./heavy-service').then(m => m.HeavyService))
  • 适用于大型应用中按需加载模块的场景

作者态度

暂时不考虑。服务一般不会特别重。

15. 装饰器组合

  • 支持将多个装饰器组合成一个,减少样板代码
  • 比如 const InjectOptional = compose(Inject, Optional) 或者 @Inject(token, { optional: true, self: true })
  • 当前需要在同一个属性上堆叠多个装饰器,可读性不够好

作者态度

暂时不考虑。

16. 测试辅助工具

  • 提供 createTestingContainer() 工厂方法,内置 mock/stub 支持
  • 支持 container.rebind(token).toConstantValue(mockInstance) 方便替换依赖
  • 参考 NestJS 的 Test.createTestingModule() 设计

作者态度

暂时不考虑。

17. 性能优化方向

  • 当前 _getInjectProperties 每次解析实例时都会遍历继承链合并元数据,可以考虑缓存合并结果
  • Binding._resolvers 使用字符串映射到方法名再通过 (this as any)[resolver] 调用,可以直接映射到函数引用,避免字符串查找
  • 大量绑定时,Map 的性能已经很好,但如果需要极致性能,可以考虑分层索引

作者态度

暂时不考虑。

18. 错误体验优化

  • 当前错误信息已经不错,但可以参考 Angular 的错误码系统(如 NG0200: Circular dependency),每个错误附带文档链接
  • 在开发模式下提供更详细的诊断信息,生产模式下精简错误消息以减小包体积
  • 循环依赖错误可以展示完整的依赖路径图(A → B → C → A)

作者态度

暂时不考虑。本项目不存在循环依赖问题。

19. 与框架集成的标准化

  • 当前已有 @kaokei/use-vue-service 与 Vue 集成,可以考虑提供更通用的集成接口
  • 定义一个 ContainerAdapter 接口,让不同框架(React、Vue、Svelte)都能以统一方式接入
  • 参考 InversifyJS 的 inversify-reactinversify-express-utils 等生态

作者态度

暂时不考虑。目前只提供给 @kaokei/use-vue-service 使用。不考虑react,因为react的基于计算的模型,天然和响应式数据模型不太契合。除非引入编译器,否则很难提供友好的API。

20. 不可变容器(Frozen Container)

  • 提供 container.freeze() 方法,冻结后不允许再添加/修改绑定
  • 适用于应用启动完成后锁定配置,防止运行时意外修改
  • 参考 Spring 的 ApplicationContext 在 refresh 后不可变的设计

作者态度

暂时不考虑。