先记住这个答案
若对象创建需要组合配置、查分支或注入依赖,而这些步骤散落在多个调用点,那么新增类型或更改参数往往要改所有new语句。工厂将这一过程封装进单一入口,让调用方只表达意图,创建细节与类型选择都在内部处理。不过,只有当下述信号同时满足时才值得引入:重复的new代码、条件依赖或装配复杂度高,且这些逻辑预计会变化。
- 重复的
new代码是工厂信号 - 依赖装配或分支时优先考虑工厂
- 不要为简单创建过度引入工厂
散落`new`的复制粘贴成本
当对象构造需要传多个参数,有些来自环境变量,有些来自上一个模块计算,每个调用点都重复写new Foo(...)时,这些参数的顺序和含义必须同步维护。一旦某天新增依赖或调整构造签名,改动必须遍历所有调用点,而遗漏不会立刻报错,要到支付或请求时才暴露。
更隐蔽的是类型分支:不同业务页面都各自用if (type === 'wechat')创建对应处理器。条件判断本身、参数来源和异常兜底被复制到各处,以后添加'银联'时,只要某处忘记改,该入口就使用缺失对象直接崩掉。此时创建逻辑应该内聚为单一入口。
一个支付处理器的工厂重构过程
假设订单系统原先在收款、退款、对账三处分别写new WechatPay({ appid, secret })和new AlipayPay({ pid, key })。每次加新渠道,都要在三个业务流程里找到对应位置,更新条件与参数。某次只改了两处,第三处遗漏后,对账入口抛出了TypeError。
重构后定义PaymentFactory.create(type, context),内部持有渠道映射与配置。调用方统一传渠道名和上下文,工厂负责从配置读取密钥并返回实例。新增强银联时,只需在工厂注册union,业务代码完全不变,也避免了三处条件不一致。
工厂不是为所有`new`准备的
如果对象只是new Car(500),构造参数单且不会变,且全系统只有一处调用,那么引入工厂就是多余的间接层。因为它要求读者先查看工厂返回什么,再跳回构造函数,阻断了直接理解。
另一种情况是创建方式虽重复,但结构简单且稳定——比如三处都创建同一个内部工具类。只要它未来不会增加类型或参数,复制几次也比单工厂更加清晰。只有当创建逻辑自身具有变化轴时,工厂才有隔离价值。
容易答错的地方
- 只要`new`出现多就应改工厂
- 判断信号不是出现次数,而是创建逻辑是否可能独立演化。如果两处
new完全相同且稳定,工厂不会带来收益,反而增加一层抽象。工厂要解决的是因重复引起的高修改成本。 - 工厂一定比直接`new`更适合JavaScript
- 工厂函数能隐藏原型和
this细节,但取决于实现,它可能让调用方无法直接通过instanceof判断具体类型(除非工厂内部显式构造对象),因此类型识别通常更间接。当对象结构简单,用构造器时创建意图更清晰,所以不应当用单一标准套用所有代码。
面试官还会怎么问?
如何用代码扫描迅速排查散落的`new`?
搜索同一构造函数的出现点,逐处检查周围是否有相同类型判断和参数拼装。如果出现频次高且每次代码相似,就应考虑收敛为工厂,否则可以先记录为可疑点。
工厂模式和构造器在异常处理上有什么区别?
构造器只能通过throw告知失败,调用方需自己捕获;工厂可捕获内部创建异常并转为统一的业务错误类型,还能对部分参数做默认兜底,但它必须面向应用场景提供策略。
工厂内部巨大switch如何继续拆分?
把switch换成注册表映射和创建函数数组,让工厂只做分发查找。新类型注册到结构体里,而不是每次打开文件扩展case。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。