TypeScript中依赖调用顺序的建造者模式(编译器限制?)
依赖调用顺序的建造者模式类型检查问题
我需要实现一种依赖调用顺序的建造者模式,要求调用某些方法后无法再调用对应方法。下方代码在运行时会正确报错,但我希望能编写一个版本,让错误的最后一行在类型检查阶段就失败。
const toppings = Symbol(); const burgerBuilder = { [toppings]: [] as string[], withMayo() { const {withMayo, ..._this} = this; return {..._this, [toppings]: [...this[toppings], 'mayo']}; }, withKetchup() { const {withKetchup, ..._this} = this; return {..._this, [toppings]: [...this[toppings], 'ketchup']}; }, order() { return `bun-patty-${this[toppings].map(topping => `${topping}-`).join('')}bun` } }; console.log(burgerBuilder.order()); // [LOG]: "bun-patty-bun" console.log(burgerBuilder.withMayo().order()); // [LOG]: "bun-patty-mayo-bun" console.log(burgerBuilder.withMayo().withKetchup().order()); // [LOG]: "bun-patty-mayo-ketchup-bun" console.log(burgerBuilder.withMayo().withKetchup().withMayo().order()); // [ERR]: burgerBuilder.withMayo(...).withKetchup(...).withMayo is not a function
提问:
- 这是TypeScript编译器的硬限制吗?是否存在如使用类的变通方案?
- 这种需求是否有公认名称,以便提交到TypeScript问题追踪器?
回答
1. 不是硬限制,可通过类+泛型实现类型级别的调用顺序约束
TypeScript完全支持通过泛型和类型收缩实现这种一次性方法调用的建造者模式,无需依赖运行时删除方法。核心思路是用泛型标记已调用的方法,在方法返回的新实例类型中移除对应方法,让TypeScript在编译阶段就拦截非法调用。
示例实现:
type UsedToppings = 'mayo' | 'ketchup'; class BurgerBuilder<T extends UsedToppings[] = []> { private toppings: UsedToppings[] = []; withMayo(): BurgerBuilder<[...T, 'mayo']> { const newBuilder = new BurgerBuilder<[...T, 'mayo']>(); newBuilder.toppings = [...this.toppings, 'mayo']; return newBuilder; } withKetchup(): BurgerBuilder<[...T, 'ketchup']> { const newBuilder = new BurgerBuilder<[...T, 'ketchup']>(); newBuilder.toppings = [...this.toppings, 'ketchup']; return newBuilder; } order(): string { return `bun-patty-${this.toppings.map(t => `${t}-`).join('')}bun`; } } // 通过类型别名过滤已调用的方法 type BuilderWithout<T extends UsedToppings[]> = Omit<BurgerBuilder<T>, { [K in UsedToppings]: K extends T[number] ? K : never }[UsedToppings]>; // 创建初始实例,确保所有方法可用 function createBurgerBuilder(): BuilderWithout<[]> { return new BurgerBuilder() as BuilderWithout<[]>; } // 测试 const builder = createBurgerBuilder(); builder.order(); // 正常 builder.withMayo().order(); // 正常 builder.withMayo().withKetchup().order(); // 正常 builder.withMayo().withKetchup().withMayo(); // TypeScript编译报错:属性'withMayo'不存在于目标类型
2. 该需求的公认名称是「顺序约束的建造者模式」或「一次性方法调用建造者」
在TypeScript社区和问题追踪中,这类需求通常被描述为 "Builder pattern with sequential method call constraints"(顺序约束的建造者模式),或更直白的 "One-time method call builder"(一次性方法调用建造者)。提交Issue时可使用这类关键词,附上示例代码说明需要编译阶段的类型检查支持,而非仅运行时错误。
内容的提问来源于stack exchange,提问作者Carl Patenaude Poulin
相关产品推荐
相关产品推荐

