TypeScript中如何限制仅通过Builder模式创建Product对象?
如何确保只能通过Builder模式创建Product对象?
问题原因
TypeScript采用结构类型系统:只要对象的结构与目标类型的属性匹配,就会被判定为该类型的实例,无论是否通过类的构造函数创建。你写的对象字面量{name: 'Laptop'}结构完全符合Product的定义(包含必填name属性,可选属性缺失也允许),因此TypeScript允许它赋值给Product[]类型。
解决方案
方法1:添加私有属性(最简洁高效)
在Product类中添加一个私有属性,外部对象字面量无法拥有该属性,结构上就无法匹配Product类型,TypeScript会直接报错。
修改后的代码:
class Product { name: string price?: number discount?: number // 私有属性仅用于结构区分,无需赋值 private _privateMarker: never; private constructor(name: string){ this.name = name } static builder(name: string): ProductBuilder { return new ProductBuilder(new Product(name)) } } class ProductBuilder { private product: Product constructor(product: Product) { this.product = product } withPrice(price?: number) { this.product.price = price; return this; // 链式调用优化,可选 } withDiscount(discount?: number) { this.product.discount = discount; return this; // 链式调用优化,可选 } build(): Product { // 可添加验证逻辑,比如校验name非空、price非负等 if (!this.product.name) { throw new Error("产品名称不能为空"); } return this.product } } // 正常工作 let product = Product.builder('PC') .withPrice(125.6) .build() // 现在会报错,符合预期 let items: Product[] = [ { name: 'Laptop' } ]
方法2:使用品牌类型(Branded Types)
通过TypeScript的类型别名和交叉类型,给Product添加一个专属品牌标识,确保只有通过构造函数创建的实例才具备该标识。
代码示例:
// 定义品牌类型工具 type Brand<T, U> = T & { __brand: U }; // 定义带品牌标识的Product类型 type Product = Brand<{ name: string; price?: number; discount?: number; }, 'Product'>; // 实际实现类,隐藏品牌细节 class ProductImpl implements Omit<Product, '__brand'> { name: string; price?: number; discount?: number; private constructor(name: string) { this.name = name; } static builder(name: string): ProductBuilder { return new ProductBuilder(new ProductImpl(name) as Product); } } class ProductBuilder { private product: Product; constructor(product: Product) { this.product = product; } withPrice(price?: number) { this.product.price = price; return this; } build(): Product { return this.product; } } // 正常工作 let product = ProductImpl.builder('PC').withPrice(125.6).build(); // 报错:对象字面量缺少__brand属性 let items: Product[] = [ { name: 'Laptop' } ];
可选增强:冻结实例
在build方法中冻结Product实例,防止外部修改属性,进一步保障实例的不可变性:
build(): Product { // 验证逻辑 Object.freeze(this.product); return this.product; }
内容的提问来源于stack exchange,提问作者Rohat Sagar
相关产品推荐
相关产品推荐

