Chapter 02 · 创建型

创建型:隔离「对象怎么来」这一处变化

01 用折扣例子立起了主线——封装变化、读意图,并给出第一个模式策略。这一章把同一副眼光对准第一类具体的变化:对象是怎么被创建的。普通代码到处写 new SpecificClass(),把调用方钉死在具体类上;创建型模式就是把这处 new 隔离起来。读完,创建家族五个成员会沿三条轴排开,单例为什么成了反面教材也会讲清。

本章你将建立的 schema

  • 创建型模式只做一件事:隔离「new 哪个类 / 怎么 new」这一处变化,让调用方不再依赖具体类。
  • 创建家族的三条区分轴:造单个 vs 造一族 · 用继承 vs 用组合 · 关注结果 vs 关注构造过程。
  • 工厂方法用继承(呼应 01.5 的「继承少数派」),抽象工厂用组合——同一族变化,两种隔离手段。
  • 简单工厂只是重构踏脚石,不是 GoF 模式;单例则是「机制本身就是反模式根因」的范例。

2.1会变的不只是算法,还有「对象怎么来」

01 隔离的是「折扣怎么算」——一处行为上的变化。但变化不止行为这一种。还有一处变化藏得更深、也更普遍:一个对象是如何被创建出来的。先看它怎么咬人。

设想一个导出模块,要把报表写成不同格式。第一版只支持 PDF:

naive-export · 第一版伪代码
function export(report):
    writer = new PdfWriter()          // ← 直接 new 了一个具体类
    writer.write(report)

需求扩张:还要支持 Excel、CSV、HTML。new PdfWriter() 这一行被改成一条 if-else,而且每个用到导出的地方都得抄一遍这条链:

naive-export · 第四版伪代码
function export(report, format):
    if format == "pdf":
        writer = new PdfWriter()
    else if format == "excel":
        writer = new ExcelWriter()
    else if format == "csv":
        writer = new CsvWriter()
    // …新增 HtmlWriter 又要回来改这里,且别处的同款链也得跟着改
    writer.write(report)
这处 new 带来的失败模式

① 调用方被钉死在具体类上——export 必须 import 每一个 XxxWriter 的实现,与它们强耦合。

② 创建逻辑散落各处——只要有第二个地方需要 writer,这条 if-else 就被复制一份,新增格式时多处同步修改。

③ 违反开闭原则——「对扩展开放、对修改关闭」被打破:加一种格式,必须回去改已经写好的分支,而不是只加新代码。

根因和 01 的折扣链是同一类:「该实例化哪个类」是会变的,但它和「不变的使用逻辑」(writer.write(report))焊死在了一起。把 01 的「封装变化」这副眼光搬过来,要隔离的那处变化,这次就叫「对象怎么来」。创建型模式整族,就是对这一处变化的不同回应。

2.2踏脚石:简单工厂(不是 GoF 模式)

简单工厂:把分散的 new 集中到一个函数里,靠一个参数挑出该造哪个产品。

为什么先讲它 / 它分离出什么

它把上面那条散落各处的 if-else 收拢到唯一一处,让调用方从「认识每个具体类」退回到「只认识一个工厂 + 一个产品接口」。分离出去的,是「new 哪个类」这处选择——只是手段最原始:一个集中的条件分支。

simple-factory · 集中那处 new伪代码
interface Writer:
    write(report)

// 所有 new 集中在这一个函数里(它就是"简单工厂")
function createWriter(format): Writer
    if format == "pdf":   return new PdfWriter()
    if format == "excel": return new ExcelWriter()
    if format == "csv":   return new CsvWriter()
    throw "未知格式"

// 调用方现在只认识 Writer 接口和这个工厂,不再 import 任何具体类
function export(report, format):
    writer = createWriter(format)
    writer.write(report)

机制:比文档深一层

调用方的耦合确实降了一档——失败模式 ① ② 缓解了。但代价是把痛点搬了家,没有消除:那条 if-else 还在,只是从「多处」缩成了「一处」。新增 HtmlWriter,仍然要回去改 createWriter 这个函数体,加一个分支。开闭原则(失败模式 ③)依旧没满足。

常见误解 · 它不是 GoF 的工厂

简单工厂常被当成「工厂模式」,但它不在 GoF 的 23 个模式之列。它只是一次朴素重构——把散落的 new 收口成一个函数。它的价值是教学上的:作为踏脚石暴露出「集中 new 还不够,加产品仍要改那个 switch」这个未解的问题,从而逼出后面两个真正用「封装变化」消除条件分支的 GoF 工厂。

何时不该往下走

如果产品种类很少、且基本不会再增加,简单工厂就是终点。把它升级成工厂方法或抽象工厂,只会凭空多出一堆类,换不回任何收益——这与 01 那条判断一致:没有真实的变化,就不要为变化铺设脚手架。

2.3工厂方法:把「造哪个」延迟到子类

工厂方法:在父类里留一个虚方法负责创建对象,把「实例化哪个具体类」延迟到子类去重写。

它分离出什么变化

简单工厂把「造哪个」锁在一个 if-else 里;工厂方法把这个选择从条件分支变成多态。父类定好「要用一个产品来干活」的骨架,但不说产品是哪个;具体哪个,由各个子类各自回答。新增一种产品,等于新写一个子类,父类与已有子类一行都不动——开闭原则这次真正满足了。

机制:比文档深一层

核心机制是一个虚方法当钩子,嵌在父类定好的算法骨架里。这正是 01.5 讲模板方法时那个「好莱坞原则」的结构:父类的骨架方法在需要产品时,回调子类重写的 createWriter(),而不是自己 new。换句话说,工厂方法 = 一个专门用来「创建产品」的模板方法钩子。

所以它用的隔离手段是继承,不是组合。这恰好呼应 01.5 那条提醒:「组合优于继承」是主流,但工厂方法和模板方法是故意反过来用继承的少数派。产品类和创建者类形成两条平行的继承体系——每多一种产品,就要配一个对应的创建者子类。

factory-method · worked example伪代码
interface Writer:
    write(report)

// ① 父类定好算法骨架,但把"造哪个 writer"留成一个抽象钩子
abstract class Exporter:
    abstract createWriter(): Writer        // ← 工厂方法,子类填

    // 不变的骨架:取得产品 → 使用产品。它不知道是哪个 writer
    export(report):
        writer = this.createWriter()       // 回调子类(好莱坞原则)
        writer.write(report)

// ② 每种产品 = 一个创建者子类,只回答"造哪个"这一个问题
class PdfExporter extends Exporter:
    createWriter(): return new PdfWriter()

class ExcelExporter extends Exporter:
    createWriter(): return new ExcelWriter()

// ③ 新增 CSV = 新写一个子类,Exporter 和上面两个子类一行都不改
class CsvExporter extends Exporter:
    createWriter(): return new CsvWriter()

// 客户端挑一个创建者,后续只调骨架方法
exporter = new ExcelExporter()
exporter.export(report)                    // 走 Excel 那条产品线

逐行 → 概念(不是逐行 → 语法)

① abstract createWriter() 就是「工厂方法」本体——父类只声明「这里需要一个产品」,把具体哪个留空。它和 01.5 模板方法里的 abstract body() 是同一种钩子,只不过这个钩子专门用来造对象。

② 子类不重写 export 的流程,只重写「造哪个」。PdfExporter 和 ExcelExporter 互不知晓,各管一条产品线。

③ 新增产品只加一个类,不改任何旧类——简单工厂没做到的开闭,这里做到了。区别就在于:简单工厂用条件分支选,工厂方法用子类多态选。

创建者(继承链) Exporter 骨架 + createWriter()钩子 继承 PdfExporter ExcelExporter 产品(继承链) «interface» Writer 实现 PdfWriter ExcelWriter createWriter() 产出 → 代价:产品与创建者一一对应,类数量翻倍
图 2.1(工厂方法结构)工厂方法的两条平行继承体系:左边创建者、右边产品,子类一一对应。注意:朱红色的 Exporter 用继承(实线)分发创建职责,这是 01.5 说的「继承少数派」。代价也直接写在图里——每加一种产品,左右各多一个类。
想一想

简单工厂(2.2)和工厂方法(2.3)都让调用方「只认接口、不认具体类」。既然结果看起来一样,工厂方法多出来的那堆子类到底买到了什么?换句话说,什么场景下这笔类数量的开销才划算?

展开答案(先停 10 秒再点)

买到的是开闭原则:简单工厂新增产品要回去改那个 if-else(碰旧代码);工厂方法新增产品只加一个子类(不碰旧代码)。但这笔开销只在两种场景划算:其一,父类骨架里除了「造产品」还有大量不变的公共流程,值得用继承复用(这正是它和模板方法同源之处);其二,产品种类会持续增加,且希望第三方能通过加子类来扩展、而无权改你的源码。

若产品就那么几种、且骨架里没什么公共流程,工厂方法的子类爆炸只是负担,简单工厂反而更诚实。判断依据仍是 01 那条:变化是否真实存在。

2.4抽象工厂:成族地造,而非单个地造

抽象工厂:提供一个接口,用来创建「一族相关的产品」,而不指定它们的具体类。

它分离出什么变化

工厂方法关心「造一个产品的哪个版本」;抽象工厂关心「造一整族互相搭配的产品的哪个版本」。分离出去的,是「整族产品属于哪个家族」这处变化——切换家族,就换掉整套搭配,而调用方对家族一无所知。

把导出例子升一级:报表不止要 Writer,还要配套的 Styler(样式器)。而且这两者必须同族——PDF 的内容得配 PDF 的样式,不能拿 Excel 的样式去刷 PDF。这种「必须配套」的约束,正是抽象工厂的用武之地。

abstract-factory · worked example伪代码
// 两类产品,各自一个接口
interface Writer:  write(report)
interface Styler:  decorate(report)

// ① 一个工厂接口上挂多个 create 方法 —— 用【组合】把一族产品聚到一起
interface ExportFactory:
    createWriter(): Writer
    createStyler(): Styler

// ② 每个家族 = 一个工厂实现,只产出本族产品
class PdfFactory implements ExportFactory:
    createWriter(): return new PdfWriter()
    createStyler(): return new PdfStyler()      // 同族,保证搭配一致

class ExcelFactory implements ExportFactory:
    createWriter(): return new ExcelWriter()
    createStyler(): return new ExcelStyler()

// ③ 客户端只持有一个工厂接口,产物天然同族 —— 跨族搭配在结构上无从写出
class ReportJob:
    factory: ExportFactory
    constructor(factory): this.factory = factory
    run(report):
        writer = this.factory.createWriter()
        styler = this.factory.createStyler()
        styler.decorate(report)                 // writer 与 styler 必同族
        writer.write(report)

job = new ReportJob(new PdfFactory())           // 切到 Excel 只改这一处
job.run(report)

机制:比文档深一层

第一个关键:抽象工厂用的是组合,不是继承——ReportJob 通过持有一个 ExportFactory 对象来获得创建能力。这与工厂方法(用继承)正好相对,也回到了 01.2 那两台机制:工厂方法靠继承隔离变化,抽象工厂靠组合隔离变化。这是本章对 01 主线最直接的一次印证。

第二个关键,也是最容易被略过的:族的一致性为什么有保证?不是靠什么校验逻辑,而是靠结构——writer 和 styler 都来自同一个工厂实例。只要 factory 是 PdfFactory,它两个方法吐出的就必然都是 PDF 族;想拿到一个 PDF 的 writer 配一个 Excel 的 styler,在这套结构里根本写不出来。一致性被编译期结构锁死,而非运行时检查。

出人意料 · 两者是搭积木,不是二选一

抽象工厂常被摆成工厂方法的「竞争对手」,要二选一。但拆开 PdfFactory 看:它内部每一个 createWriter() / createStyler() 方法,本身就是一个工厂方法。抽象工厂常常就是用一组工厂方法实现的——一个工厂对象上挂着多个工厂方法,每个负责本族的一类产品。两者不是对立选项,而是搭积木关系:抽象工厂是更大的那块积木,工厂方法是拼进去的小块。

代价:加一种新产品类型会震动所有工厂

抽象工厂有一处刚性代价,方向和工厂方法相反。加一个新家族(如 HtmlFactory)很便宜——只新写一个工厂类。但加一种新的产品类型(如给每族都添一个 Validator)很贵:这要在 ExportFactory 接口上加一个 createValidator() 方法,于是每一个已存在的工厂实现都被迫跟着改。产品族的「维度」一旦定下,后加维度的成本很高。

ReportJob(客户端) 只持有一个 ExportFactory,不知具体族 PdfFactory(PDF 族) 同一实例产出 PdfWriter PdfStyler ExcelFactory(Excel 族) ExcelWriter ExcelStyler 跨族连线(PdfWriter 配 ExcelStyler)画不出来 —— 一致性被结构锁死
图 2.2(抽象工厂结构)两个家族 × 两种产品。注意:朱红色那一族里,PdfWriter 与 PdfStyler 由同一个 PdfFactory 实例产出——这就是「同族一致」的来源。底部那行点明:跨族搭配的连线根本画不出来,所以混用在结构上无从发生,无需任何运行时校验。

何时不该用

只有一类产品(只有 writer,没有配套的 styler),用抽象工厂是杀鸡用牛刀——退回工厂方法即可。另外,若产品族的维度还在频繁增减(一会儿加 validator、一会儿去掉 styler),抽象工厂的「加产品类型震动所有工厂」代价会反复发作,这时它是个糟糕的选择。

2.5建造者:把「分步构造」和「最终表示」分开

建造者:把一个复杂对象的分步构造过程,与它最终的表示分离开。

它分离出什么变化

前面三个工厂关心的都是「造哪个类」——选择的是结果。建造者关心的是另一回事:类型已经定了,但这个对象怎么一步步装配出来很复杂(很多可选字段、有先后顺序、有约束)。它分离出去的不是「造哪个」,而是「构造过程本身」。

机制:比文档深一层 —— 它在替你解决「望远镜构造函数」

要理解建造者,先得看清它针对的失败模式:望远镜构造函数(telescoping constructor)。当一个对象有很多可选参数,构造函数往往被重载成一长串,参数列表越拉越长,像望远镜一节节抽出来:

telescoping · 建造者要消灭的失败模式伪代码
// 同一个对象,参数越加越多 —— 调用处完全读不出每个值是什么
new HttpRequest(url)
new HttpRequest(url, method)
new HttpRequest(url, method, headers)
new HttpRequest(url, method, headers, body, timeout, retries, useCache)

// 调用现场:一排裸值,谁知道 true 和 30000 是哪个参数?
req = new HttpRequest("/api", "POST", h, b, 30000, 3, true)

建造者的机制是用一串命名的步骤方法替换掉这条参数长链,最后用一个 build() 收口。每一步都自带名字、可选可省、可带默认值,顺序也由调用方控制。复杂度从「一个巨型构造函数签名」转移到「一串小而清晰的步骤」。

builder · worked example伪代码
class RequestBuilder:
    // 内部攒一个半成品,字段都有默认值
    private req = new HttpRequest()

    // ① 每个步骤方法配置一处,返回 this 以便链式调用
    url(u):      this.req.url = u;          return this
    method(m):   this.req.method = m;       return this
    header(k,v): this.req.headers.add(k,v); return this
    timeout(ms): this.req.timeout = ms;     return this

    // ② build() 收口:此处可做完整性校验,再交出成品
    build(): HttpRequest
        if this.req.url == null: throw "url 必填"
        return this.req

// ③ 调用现场:每个值都自带名字,可选项随用随加,顺序自由
req = new RequestBuilder()
        .url("/api")
        .method("POST")
        .timeout(30000)
        .build()                            // ← 只在这一步拿到成品

逐行 → 概念

① 每个步骤方法只配置一处、返回 this。望远镜里那排裸值,现在变成 .timeout(30000) 这种自带名字的调用——可读性是建造者最实在的收益。

② build() 是唯一的出口。把校验集中在这里,意味着半成品永远不会流出去:对象要么没造出来,要么造出来就是完整合法的。

③ 调用方按需挑步骤、自定顺序——「构造过程」被它掌控,而 HttpRequest 这个「最终表示」对此一无所知。这就是「分步构造与表示分离」的字面落地。

代价与何时不该用

代价很直白:多一个 builder 类,且它的字段要和目标对象的字段保持同步。判断标准只有一条——只有当「可选 / 有序参数很多」时才划算。如果一个对象就两三个必填参数,直接用构造函数;为它套一个建造者,是把简单问题复杂化,属于过度设计。建造者是用来驯服「望远镜构造函数地狱」的,没有那个地狱,就不需要它。

2.6单例:为什么它从模范生变成反面教材

单例:保证一个类只有一个实例,并提供一个全局访问点来取它。

它想分离什么 / 它的处境

单例想管的是「实例的数量与生命周期」这处变化——某些对象(配置、连接池)逻辑上只该有一个。意图本身合理。但它实现意图的机制,恰恰是它如今被广泛视为反模式的根因。这一节的重点不在「怎么写单例」,而在「为什么它的机制内禀地有害」。

singleton · 机制本身就是病灶伪代码
class Config:
    // ① 私有构造函数:外部不能 new,数量被锁成一个
    private constructor() { … }

    // ② 静态字段持有唯一实例 + 静态访问器作为全局入口
    private static instance = null
    static getInstance(): Config
        if Config.instance == null:
            Config.instance = new Config()
        return Config.instance

// 任何地方都能伸手拿到它 —— 这正是问题所在
db.connect(Config.getInstance().dbUrl)

机制:比文档深一层 —— 病根就在这两行

单例的两件套机制是:私有构造函数(锁死数量)+ 静态访问器(全局取用)。问题在于,这套机制带来的坏处不是误用造成的,而是机制本身内禀的:

机制内禀的两处伤害

① 它是一个伪装过的全局变量。静态访问器让任何代码都能在任何地方伸手取到它,无需声明依赖。于是「谁依赖了 Config」散落全代码、不可见——这正是全局状态一直以来的老问题,只是被一个类的外壳包装了起来。

② 它破坏单元测试隔离。静态实例在整个进程里共享、且跨测试用例存活:测试 A 改了单例状态,测试 B 就被污染。更糟的是,调用方直接调 getInstance() 而非接收一个传入的依赖,所以测试时无法替换成 mock。可测试性被私有构造函数从根上掐断了。

注意:上面两条无法靠「小心使用」绕开——它们是「私有构造 + 静态访问」这套机制的直接产物。这与 01 反复强调的「面向接口编程 + 依赖通过组合传入」正好相反:单例既不面向接口,也不把自己作为依赖传入,而是让别人主动来取。两条 01 的核心机制,它一条都不占。

现状(截至 2026) · 单例被广泛视为反模式

单例曾是最好教的模式,如今几乎成了公认的反模式:它隐藏全局状态、破坏测试隔离、制造看不见的耦合。现代做法是依赖注入(DI):把对象的生命周期交给一个 DI 容器管理——由容器保证「整个应用只有一个 Config 实例」,再主动注入给需要它的地方。这样既拿到了「全局唯一」的语义,又保留了「依赖显式、可在测试里替换」的好处。「逻辑上唯一」这个需求是真的;用单例模式去实现它,在 2026 已不是推荐答案。

想一想

「全局只要一个实例」这个需求本身没错。既然如此,为什么用 DI 容器去保证「唯一」,就避开了单例的两处伤害,而单例自己却避不开?两者最终不都只有一个实例吗?

展开答案(先停 10 秒再点)

差别不在「有几个实例」,而在依赖是「被取用」还是「被注入」。单例靠静态访问器,让调用方主动伸手去取(Config.getInstance())——这个调用硬编码在代码里,依赖关系不出现在签名上、也换不掉。DI 则把那个唯一实例从外部传进调用方(通过构造函数参数等),于是:依赖在签名上显式可见;测试时传一个 mock 就能替换;「唯一」这件事由容器在外部保证,与调用方解耦。

一句话:单例把「唯一」和「全局静态取用」捆死在一起,后者才是病根;DI 把这两件事拆开,只保留「唯一」,丢掉「静态取用」。这又一次印证 01 的判断——把会变 / 有害的那处(取用方式)隔离出去,正是「封装变化」的思路。

2.7辨析:把创建家族按三条轴排开

五个成员讲完,真正要记住的不是各自的类图,而是它们沿哪几条轴彼此区分——这正是 01 主线「读意图」在创建家族里的具体形态。三条轴:造单个还是造一族 · 用继承还是用组合 · 关注结果还是关注构造过程。

表 2.1 · 创建家族的三轴辨析(读意图,不读类图)
模式造单个 / 一族继承 / 组合结果 / 过程一句话意图
简单工厂单个都不是(一个条件分支)结果把散落的 new 收口到一处(踏脚石)
工厂方法单个继承结果把「造哪个」延迟到子类重写
抽象工厂一族组合结果成套地造相关产品,保证同族搭配
建造者单个组合过程把复杂对象的分步构造与表示分离
单例单个(且仅一个)都不是(静态)结果(+ 生命周期)锁定实例数量(2026 已不推荐)
读这张表的方法

三条轴里,最值钱的是「继承 / 组合」这一列对照着看:工厂方法用继承、抽象工厂用组合——它俩名字像、都叫「工厂」,但隔离手段正好分属 01.2 的两台机制。这就是「类图相近、读意图区分」在创建家族里的样子:不看它们长得像不像,看它们分离的是单个还是一族、用的是哪台机制。

要造一个对象 要的是一整族产品? 是(组合) 抽象工厂 否(单个) 构造过程很复杂? 是(过程) 建造者 否(关注结果) 要靠子类扩展产品线? 是(继承) 工厂方法 否(就几种) 简单工厂(够用即止)
图 2.3(创建型决策树)从「要造一个对象」往下,每一步问的都是意图层面的问题(要不要一族?过程复不复杂?靠不靠子类扩展?),而不是「类图长什么样」。注意:右侧四个出口对应表 2.1 的四行;走到最底的简单工厂用虚线框,提示它是踏脚石而非终点。单例不在树上——「逻辑唯一」交给 DI,不必走创建型这棵树。

§本章 self-check

先合上教程,把答案写在纸上或编辑器里,写完再展开对照。直接展开等于把这一节当再读一遍。

  1. 简单工厂把那条 if-else 从「多处」收成了「一处」,但有一个失败模式它没解决,工厂方法才解决。是哪一个?差别出在「选具体类」用了什么手段?
  2. (回调 01) 工厂方法用继承、抽象工厂用组合。把它们各自对应到 01.2 讲的两台机制(面向接口编程 / 组合优于继承),并说出工厂方法为什么属于 01.5 的「继承少数派」。
  3. 抽象工厂保证「同族产品不会被混用」,靠的是运行时校验,还是别的?用一句话说清这个一致性从哪来。
  4. 建造者和工厂家族,分离的变化处在本质上不同。工厂分离的是「______」,建造者分离的是「______」。各填一个词,并说出建造者针对的那个具体失败模式叫什么。
答案(先做完再展开)
  1. 没解决的是开闭原则:简单工厂新增产品仍要回去改 createWriter 那个条件分支(碰旧代码);工厂方法新增产品只加一个子类。差别在手段:简单工厂用条件分支选具体类,工厂方法用子类多态选。
  2. 工厂方法 = 继承(父类留虚方法钩子,子类重写决定造哪个);抽象工厂 = 组合(客户端持有一个工厂对象,委托它创建)。工厂方法属于 01.5 的「继承少数派」,因为它和模板方法同源——都是「父类定骨架、子类重写钩子」,只不过这个钩子专门用来造对象;它没有「持有一个接口」,而是靠继承分发创建职责。
  3. 不是靠运行时校验,而是靠结构:writer 和 styler 都来自同一个工厂实例,只要工厂是 PdfFactory,两个产物就必然同族,跨族搭配在代码里根本写不出来——一致性被编译期结构锁死。
  4. 工厂分离的是「结果(造哪个类)」,建造者分离的是「过程(怎么一步步装配)」。建造者针对的失败模式叫望远镜构造函数(telescoping constructor)——可选参数太多导致构造函数签名越拉越长、调用现场一排读不懂的裸值。
进阶挑战 · 刚好够不着

同一段需求,工厂方法还是抽象工厂?

一个跨平台 UI 库要支持 Windows、macOS 两套外观。需求 A:只需要造按钮一种控件,但两套外观的按钮长得不同。需求 B:除按钮外,还要造复选框、滚动条,且同一界面里这三种控件必须是同一套外观(不能 Windows 按钮配 macOS 滚动条)。问题:A 和 B 各自该落到创建家族的哪个模式?并说明——当需求从 A 演进到 B 时,代码结构上发生的关键一步变化是什么?

提示(卡住再展开)

A 只有「一类产品的多个版本」——这是工厂方法的形状(一个 createButton() 钩子,Windows / macOS 两个创建者子类)。B 有「一族必须配套的产品」并要锁同族一致——这是抽象工厂的形状(一个工厂接口挂 createButton / createCheckbox / createScrollbar,两个家族工厂实现)。

关键一步变化:从「一个工厂方法」长成「一个工厂对象上挂一组工厂方法」——回到 2.4 那个出人意料的点,抽象工厂往往就是用一组工厂方法实现的,B 不是推翻 A,而是把 A 那块积木拼进更大的积木里。判断依据始终是 01 的那句:会变的到底是「一个产品」还是「一整族产品」。