抽象工厂新增产品族和新增产品等级有什么区别?
简化版
抽象工厂新增产品族比较容易,通常新增一套具体产品和一个具体工厂即可;新增产品等级比较麻烦,因为要修改抽象工厂接口,并让所有具体工厂补齐新方法。它适合产品等级稳定、产品族经常扩展的场景。
详细版
假设有这个抽象工厂:
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 逃避某个族不支持新等级。 |
| 退回更轻方案的条件 | 把不稳定列拆到可选能力工厂,或重新划分产品族。 |
抽象工厂对所有变化并非都开放。它刻意固定列集合,以换取整行切换的一致性;如果系统最常发生的是新增列,工厂接口和所有行实现会频繁联动,此时应拆分工厂边界或选择组合方式。
七、落地验收清单
- 为每个具体工厂跑同一组契约测试,确认每个创建方法都返回正确产品等级。
- 给产品暴露
familyId()仅用于测试,校验同一工厂创建的所有产品 familyId 一致。 - 加入一个假产品族,记录是否只新增一行实现和装配配置,而未修改旧工厂。
- 模拟新增一个产品等级,评估抽象接口和全部具体工厂的真实修改成本。
- 检查公共产品接口是否出现 AWS、MySQL、Windows 等具体实现词汇。
- 检查具体工厂是否只做组装与创建,没有吞入完整业务流程。
- 若产品持有连接或线程池,明确由工厂、容器还是调用方负责关闭。
- 若运行期按租户切换工厂,验证路由缓存、凭证刷新和租户隔离。
记忆钩子:行扩展成本约随列数 M 走,列扩展成本约随行数 N 走。
八、常见误区与追问
- 误区:新增产品等级只需加一个产品类。 还要改抽象工厂和每个具体工厂。
- 误区:给接口加默认方法返回 null 就消除了成本。 把编译期完整性问题推迟成运行期空指针。
- 误区:新增产品族完全零修改。 装配配置或注册表通常仍需接入,但旧族实现可保持不变。
- 追问:为什么称为二维扩展? 产品族是一维,产品等级是另一维,具体产品位于交叉单元格。
- 追问:等级经常新增怎么办? 拆分工厂接口、使用能力组合或改用注册式创建。
- 追问:如何评估接口稳定性? 查看历史变化:新增厂商多还是新增产品种类多。
九、加强记忆
抽象工厂是“加一行容易,加一列困难”。一行是产品族,一列是产品等级;面试里讲清这个二维扩展成本,基本就抓住了抽象工厂的灵魂。