← 返回题目列表

抽象工厂新增产品族和新增产品等级有什么区别?

高频 困难 第 13 / 25 题 更新于 2026/07/28
抽象工厂模式产品族产品等级开闭原则

简化版

抽象工厂新增产品族比较容易,通常新增一套具体产品和一个具体工厂即可;新增产品等级比较麻烦,因为要修改抽象工厂接口,并让所有具体工厂补齐新方法。它适合产品等级稳定、产品族经常扩展的场景。

详细版

假设有这个抽象工厂:

interface UIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

新增产品族,例如新增 Linux 风格:

  • 新增 LinuxButton;
  • 新增 LinuxCheckbox;
  • 新增 LinuxFactory;
  • 原有 WindowsFactory、MacFactory 不需要改。

新增产品等级,例如新增 Menu:

  • 修改 UIFactory,增加 createMenu()
  • WindowsFactory 要实现 createMenu()
  • MacFactory 要实现 createMenu()
  • LinuxFactory 也要实现 createMenu()
  • 还要新增各平台 Menu 具体产品。

所以抽象工厂对产品族扩展友好,对产品等级扩展不友好。面试时可以主动指出这个优缺点,说明你理解它的适用边界。

完整版教学

一、为什么新增产品族比较自然

抽象工厂本来就是按产品族组织创建逻辑的。每个具体工厂对应一族产品,新增一族就新增一个具体工厂。

这种扩展不会影响已有产品族,也不会破坏旧工厂的创建逻辑,因此比较符合开闭原则。

二、为什么新增产品等级会牵动全局

产品等级体现在抽象工厂的方法上。只要增加一个新的产品等级,就意味着抽象工厂接口要多一个创建方法。

接口一改,所有具体工厂都必须实现,否则编译都过不了。这是抽象工厂最典型的结构性代价。

三、这个取舍来自模式目标

抽象工厂的目标不是让任何变化都变简单,而是让“整套产品族切换”变简单。它用接口固定产品等级,再让具体工厂填充不同产品族。

如果你的系统经常新增产品等级,抽象工厂可能不是最合适的第一选择。

四、工程上怎么缓解新增产品等级的痛点

可以考虑几种方式:

  • 把不稳定产品等级拆成独立工厂;
  • 使用默认方法提供兼容实现,但要谨慎处理运行时行为;
  • 把某些可选能力抽成能力接口;
  • 重新审视抽象边界,避免一个工厂接口承担过多产品。

这些方法不能完全消除代价,但可以让变化影响面更可控。

五、把“加一行”和“加一列”的修改文件列出来

已有 Windows、Mac 两行和 Button、Checkbox 两列。新增 Linux 是加一行:两个产品加一个工厂;新增 Menu 是加一列:抽象工厂、两份旧工厂和各平台产品都要变化,行数越多联动越大。

原矩阵 2×2:
          Button   Checkbox
Windows   WB       WC
Mac       MB       MC
加 Linux 行 -> LB + LC + LinuxFactory
加 Menu 列 -> UIFactory.createMenu()
Windows/Mac/LinuxFactory 全部补 createMenu()
行扩展局部,列扩展横切

矩阵的每一行是一族兼容产品,每一列是一个产品等级。客户端选择一行对应的具体工厂,再从该工厂取得各列产品;如果允许调用方绕过工厂分别选择每个具体类,产品族一致性的结构性保证就消失了。

六、二维扩展成本与模式边界

评审维度本题结论
产品族不变量每行工厂完整提供当前接口声明的全部产品等级。
新增一行(产品族)新增 M 个具体产品和一个工厂,M 为现有产品等级数。
新增一列(产品等级)修改抽象接口并修改 N 个具体工厂,N 为现有产品族数,还需新增 N 个产品。
不应由工厂承担用默认返回 null 逃避某个族不支持新等级。
退回更轻方案的条件把不稳定列拆到可选能力工厂,或重新划分产品族。

抽象工厂对所有变化并非都开放。它刻意固定列集合,以换取整行切换的一致性;如果系统最常发生的是新增列,工厂接口和所有行实现会频繁联动,此时应拆分工厂边界或选择组合方式。

七、落地验收清单

  1. 为每个具体工厂跑同一组契约测试,确认每个创建方法都返回正确产品等级。
  2. 给产品暴露 familyId() 仅用于测试,校验同一工厂创建的所有产品 familyId 一致。
  3. 加入一个假产品族,记录是否只新增一行实现和装配配置,而未修改旧工厂。
  4. 模拟新增一个产品等级,评估抽象接口和全部具体工厂的真实修改成本。
  5. 检查公共产品接口是否出现 AWS、MySQL、Windows 等具体实现词汇。
  6. 检查具体工厂是否只做组装与创建,没有吞入完整业务流程。
  7. 若产品持有连接或线程池,明确由工厂、容器还是调用方负责关闭。
  8. 若运行期按租户切换工厂,验证路由缓存、凭证刷新和租户隔离。

记忆钩子:行扩展成本约随列数 M 走,列扩展成本约随行数 N 走。

八、常见误区与追问

  • 误区:新增产品等级只需加一个产品类。 还要改抽象工厂和每个具体工厂。
  • 误区:给接口加默认方法返回 null 就消除了成本。 把编译期完整性问题推迟成运行期空指针。
  • 误区:新增产品族完全零修改。 装配配置或注册表通常仍需接入,但旧族实现可保持不变。
  • 追问:为什么称为二维扩展? 产品族是一维,产品等级是另一维,具体产品位于交叉单元格。
  • 追问:等级经常新增怎么办? 拆分工厂接口、使用能力组合或改用注册式创建。
  • 追问:如何评估接口稳定性? 查看历史变化:新增厂商多还是新增产品种类多。

九、加强记忆

抽象工厂是“加一行容易,加一列困难”。一行是产品族,一列是产品等级;面试里讲清这个二维扩展成本,基本就抓住了抽象工厂的灵魂。