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

如何为TypeScript简易规则引擎类实现强类型?

TypeScript强类型规则引擎实现方案

问题描述

我正在构建一个带有addRule和execute方法的TypeScript简易规则引擎类,现有实现代码如下:

type Rule = {
  name: string;
  rule: (input: unknown) => number;
}

class RuleEngine {
  private rules: Rule[] = [];

  addRule(name: string, rule: (input: unknown) => number) {
    this.rules.push({ name, rule });
  }

  execute(initialInput: number) {
    return this.rules.reduce(
      (result, rule) => {
        result[rule.name] = rule.rule(result);
        return result;
      },
      { initialInput },
    );
  }
}

执行结果是一个以规则名为键的对象,示例如下:

const engine = new RuleEngine();

engine.addRule("ruleOne", input => input.initialInput * 2);
engine.addRule("ruleTwo", input => input.initialInput + input.ruleOne);

const result = engine.execute(10);
// result = {
//   initialInput: 10,
//   ruleOne: 20,
//   ruleTwo: 30,
// }

我希望实现以下强类型效果:

  • 后续添加的规则函数输入能基于之前添加的规则自动强类型,例如:
    // input = { initialInput: number }
    engine.addRule("ruleOne", input => input.initialInput * 2); 
    
    // input = { initialInput: number, ruleOne: number }
    engine.addRule("ruleTwo", input => input.initialInput + input.ruleOne); 
    
  • execute方法的返回结果也具备强类型:
    // result = { initialInput: number, ruleOne: number, ruleTwo: number }
    const result = engine.execute(10); 
    

尝试过泛型和重载,但重载需要手动为不同数量的规则编写定义,十分繁琐。请问该如何简化实现?这是否属于过度设计?我知道这本质类似管道操作符,但需要为规则命名以便区分大量规则。

简化实现方案

可以通过泛型累积类型实现自动的强类型推导,无需手动编写重载。核心思路是让RuleEngine类的泛型参数跟踪已添加规则的字段类型,每次调用addRule时返回一个扩展了新规则字段的新引擎实例:

type Rule<T extends Record<string, number>> = {
  name: string;
  rule: (input: T) => number;
};

class RuleEngine<T extends Record<string, number> = { initialInput: number }> {
  private readonly rules: Rule<any>[];

  constructor(rules: Rule<any>[] = []) {
    this.rules = rules;
  }

  addRule<K extends string>(
    name: K,
    rule: (input: T) => number
  ): RuleEngine<T & { [key in K]: number }> {
    // 返回新的引擎实例,保留现有规则并添加新规则
    return new RuleEngine([...this.rules, { name, rule }]);
  }

  execute(initialInput: number): T {
    return this.rules.reduce(
      (result, rule) => {
        // 类型断言用于绕过临时的类型限制,实际逻辑是安全的
        (result as any)[rule.name] = rule.rule(result);
        return result;
      },
      { initialInput } as T
    );
  }
}

使用示例

// 初始引擎,类型为 RuleEngine<{ initialInput: number }>
const baseEngine = new RuleEngine();

// 添加ruleOne后,引擎类型自动扩展为 RuleEngine<{ initialInput: number; ruleOne: number }>
const engineWithRuleOne = baseEngine.addRule("ruleOne", input => input.initialInput * 2);

// 添加ruleTwo时,input参数自动包含initialInput和ruleOne字段
const finalEngine = engineWithRuleOne.addRule("ruleTwo", input => input.initialInput + input.ruleOne);

// execute返回类型为 { initialInput: number; ruleOne: number; ruleTwo: number }
const result = finalEngine.execute(10);
// 可以直接访问result.initialInput、result.ruleOne、result.ruleTwo,且类型均为number

关于是否属于过度设计的讨论

  • 合理场景:如果你的规则引擎需要处理大量命名规则,且要求在开发阶段就捕获依赖错误(比如引用不存在的规则字段、类型不匹配),这种强类型实现就很有价值——它能避免运行时才暴露的问题,提升代码可维护性,完全不属于过度设计。
  • 冗余场景:如果只是处理少量简单规则,且对类型检查要求不高,原生的弱类型实现已经足够,此时强类型实现可能显得冗余。
  • 对比管道操作符:管道模式更适合无命名的线性流程,而命名规则的场景下,强类型引擎能让每个规则的依赖关系清晰可见,这是管道模式无法替代的,因此这种设计是合理的。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 00:12:04