先记住这个答案
简单工厂用一个入口函数按参数拼出具体对象,适合种类少且稳定的阶段;需要给同一产品加变体时改为工厂方法,让子类覆盖创建方法而不再修改公共代码;当有多个关联产品需要组合使用时,抽象工厂提供一组创建接口,确保客户端取到的产品出自同一族。三者的耦合程度由方法内判断、子类覆盖和族级接口逐级加强。选择时先问变化是单产品品种,还是整套互相依赖的搭配。
- 简单工厂集中分支,新增类型需要改中心函数
- 工厂方法把创建选择下放给子类覆盖
- 抽象工厂管一簇产品,客户端选工厂不选产品
创建责任从中心走向分布
简单工厂用一个函数按参数返回具体实例,将所有分支集中到一处,让调用方避开具体类,但新增类型仍需修改工厂的中心逻辑。工厂方法则把创建方法声明在抽象工厂接口中,让子类覆盖并返回由它决定的对象,父类不关心具体是哪个类,因此新增产品可以在不改动老代码的情况下由新子类加入。两者差别从“改 if 分支”转移到“新增子类”。
从工厂方法到抽象工厂不是复杂度的提升,而是产品数量的维度扩张。一种产品需要多种实现时,工厂方法足够;当发货套餐同时包含箱子、面单和包装且三者必须彼此匹配时,抽象工厂拿出多个 create 方法,分别产生族内对应的产品对象。调用方只面对某个具体工厂,不需要自己挑选产品,就可以保证三个产出之间无跨族搭配。
仓库从单仓扩展成区域发货体系的实例
一个国际仓配系统初始只要按温度创建普通纸箱或冷藏箱,一个 createBox(type) 函数的简单工厂足以满足,新增温度类型也只是加一个分支。两个月后公司要跑美国东岸和欧洲两条专线,两地对包装箱尺寸、外保护层和面单规定都不同,每次发货需要这三样配套,简单工厂如果继续做会同时接收“区域+温度”两维参数,并手工保证三样对象来自同一区域,极易串线。
从这刻起把工厂调整为抽象工厂:定义 LogisticsFactory,上面有关联的 createCarton、createWrapper、createLabel。美国东岸与欧洲分别用一个工厂实现一整套;客户端只要从配置取区域工厂,连续调用三个方法即可完成发货前组装。没过多久加日本仓时,团队新增 JapanFactory,没有碰原有创建渠道,原来核心发货代码保持一致,这正是顺着“产品族”的变化轴选择合适层级的直接结果。
三种款式各自的失效范围
简单工厂在类型到达六七种且构造参数开始变化时变得不可控,因为中心分支很难再做组合判断,任何扩展都可能漏掉某个调用分支,这时就该考虑把“实例化谁”这个动作下沉到工厂子类。继续硬撑简单工厂只会降低可读性,也无法利用多态替换。
工厂方法的风险是类数目泡沫:每个产品配一个工厂子类,若实际产品数目不多而只是接口不同,这种额外的继承维度会让修改路径过长,维护成本反超收益。抽象工厂则要求对象间确实存在强关联和使用协作性;如果一组随机对象被强行组织成“族”,各自独立变化却永远在同一工厂里返回,本质上只是复杂版的工厂方法,并不会带来一致性收益。JavaScript 没有编译期接口,所以这些模式落实时还得依靠命名约定、测试或类型标注来守住边界。
容易答错的地方
- 简单工厂被扣反模式帽
- 它不是四人组定义的正式模式,却能充当重构起点。真正失控的并非简单工厂,而是类型无限增多又拒绝演进。它应是过渡方案,不需要一上来就套抽象工厂。
- 工厂方法和抽象工厂混为一谈
- 两者都通过工厂接口让调用方避开直接实例,但工厂方法反馈单一产品,抽象工厂回答产品族的整套创建。用抽象工厂处理单维度差异会产生大量无意义方法,扩展一次要写一套无关对象。
面试官还会怎么问?
新增一个具体类型时应如何判断该走哪种工厂?
先看变化是否属于一个产品:单产品变化只要工厂方法;若它还牵出一组相关规格(标签、包装、面单)就升级到抽象工厂。若类型数固定少于 5 且无明显套餐,可使用简单工厂保持可读。
JavaScript 没有接口,抽象工厂模式怎么落地?
一个工厂不需要 extends,只要它携带 createA、createB 等方法且调用者只用这些方法就成,实际产品一致性仍需人工保证。可用函数返回具名对象,并配合单元测试校验同一次引用所产生的产品是否同签名。
抽象工厂与建造器(构建器)模式能否混用?
可以,当族中各产品还需要复杂组装时,抽象工厂决定哪个族,具体的逐件装配交给 Builder 控制。不要把 Builder 放进抽象工厂接口中,否则会混合创建与表征两个变化点。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。