You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

依赖倒置模式下循环引用引发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模块加载机制的特性导致的:

  1. 循环引用链形成:

    • FieldTypeToClassMapping依赖StringField、IntegerField等子类模块
    • 子类模块又必须依赖父类Field模块
    • 而Field模块又直接导入了FieldTypeToClassMapping
      最终形成FieldTypeToClassMapping → 子类 → Field → FieldTypeToClassMapping的闭环引用
  2. 模块初始化时序冲突:

    • 当加载Field模块时,会优先加载它依赖的FieldTypeToClassMapping模块
    • 加载FieldTypeToClassMapping时,又会触发子类模块的加载
    • 子类模块导入Field时,Field模块还处于初始化阶段(还未完成Field类的导出),此时子类拿到的Field是undefined
    • 子类试图extends undefined,直接抛出TypeError
  3. 移至constants.ts后仍有问题的原因:

    • 主程序的模块加载顺序刚好避开了最极端的初始化冲突,让代码能正常运行
    • 但单元测试的模块加载逻辑(比如单独测试子类或Field类)会重新触发循环引用的初始化问题,导致测试崩溃

修复方案的合理性

你通过Field.create方法传入FieldTypeToClassMapping的方式,本质是打破了模块级别的循环引用:

  • 不再让Field模块直接依赖FieldTypeToClassMapping模块
  • 而是把依赖关系延迟到运行时通过参数注入,这完全符合依赖倒置原则(依赖抽象而非具体实现,通过注入传递依赖)

总结

这个问题不是你对依赖倒置模式的理解错误,而是模块循环引用引发的初始化时序问题。你的修复方案是合理的,既遵循了依赖倒置原则,又彻底解决了循环引用的问题。

内容的提问来源于stack exchange,提问作者rahbert

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 12:27:51