依赖倒置模式下循环引用引发TypeError问题咨询
依赖倒置模式下的循环引用问题与根源解析
问题描述
我在使用依赖倒置模式时碰到了循环引用问题,触发错误:TypeError: Class extends value undefined is not a constructor or null。
诡异的是,如果把field.type.ts里的内容移到src/constants.ts中,代码能正常运行,但单元测试会崩溃;要是留在原文件里,代码直接就崩了。
我怀疑自己对依赖倒置模式的理解或使用有问题,虽然已经通过在Field.create(options: FieldOptions, fieldTypeMapping: FieldTypeToClassMapping)方法里传入FieldTypeToClassMapping作为参数的方式修复了问题,但想搞清楚这个问题的根源到底是什么。
相关代码示例
field.type.ts
import { StringField } from './string.field.model'; import { IntegerField } from './integer.field.model'; ... export const FieldTypeToClassMapping = { // 存储各类Field的构造函数,用于通过类型创建实例 [FieldTypeEnum.STRING]: StringField, [FieldTypeEnum.INTEGER]: IntegerField, };
field/field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface'; import { FieldTypeToClassMapping } from 'src/model/template/field.type.to.mapping.ts' export abstract class Field { value: any; type: string; errors: string[] = []; public constructor(options: FieldOptions) { this.value = options.value; this.type = options.type; } public static create(options: FieldOptions): any { try { return new FieldTypeToClassMapping[options.type](options); } catch (e) { throw new Error(`Invalid field type: ${options.type}`); } } }
field/integer.field.ts
import { FieldOptions } from 'src/interfaces/field.options.interface'; import { Field } from './field.model'; export class IntegerField extends Field { constructor(options: FieldOptions) { super(options); } protected validateValueDataType() { this.validateDataType(this.value, "value"); } protected validateDefaultDataType() { this.validateDataType(this.defaultValue, "defaultValue"); } }
field/service.ts
const postFields = [ { type: "string", value: "John", }, { type: "integer", value: 32, }, ]; const fieldsArray = []; postFields.forEach((item) => { const field: Field = Field.create(item); fieldsArray.addField(field); }); return fieldsArray;
问题根源
这本质是模块循环引用导致的初始化顺序问题,和依赖倒置模式本身无关,是TypeScript/JavaScript模块加载机制的特性导致的:
循环引用链形成:
FieldTypeToClassMapping依赖StringField、IntegerField等子类模块- 子类模块又必须依赖父类
Field模块 - 而
Field模块又直接导入了FieldTypeToClassMapping
最终形成FieldTypeToClassMapping → 子类 → Field → FieldTypeToClassMapping的闭环引用
模块初始化时序冲突:
- 当加载
Field模块时,会优先加载它依赖的FieldTypeToClassMapping模块 - 加载
FieldTypeToClassMapping时,又会触发子类模块的加载 - 子类模块导入
Field时,Field模块还处于初始化阶段(还未完成Field类的导出),此时子类拿到的Field是undefined - 子类试图
extends undefined,直接抛出TypeError
- 当加载
移至constants.ts后仍有问题的原因:
- 主程序的模块加载顺序刚好避开了最极端的初始化冲突,让代码能正常运行
- 但单元测试的模块加载逻辑(比如单独测试子类或
Field类)会重新触发循环引用的初始化问题,导致测试崩溃
修复方案的合理性
你通过Field.create方法传入FieldTypeToClassMapping的方式,本质是打破了模块级别的循环引用:
- 不再让
Field模块直接依赖FieldTypeToClassMapping模块 - 而是把依赖关系延迟到运行时通过参数注入,这完全符合依赖倒置原则(依赖抽象而非具体实现,通过注入传递依赖)
总结
这个问题不是你对依赖倒置模式的理解错误,而是模块循环引用引发的初始化时序问题。你的修复方案是合理的,既遵循了依赖倒置原则,又彻底解决了循环引用的问题。
内容的提问来源于stack exchange,提问作者rahbert
相关产品推荐
相关产品推荐

