如何为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
相关产品推荐
相关产品推荐

