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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 12:10:28