Java 23 种设计模式:从踩坑到精通 | 抽象工厂 —— 支付/收款如何成套创建?跨平台 UI 如何一键换肤?
摘要:当系统需要同时创建多个有内在关联的产品对象时(如不同支付渠道的支付+收款,或跨平台 UI 的按钮+文本框),简单的工厂方法会导致工厂数量爆炸且难以保证产品族一致性。本文带你深入抽象工厂模式,用支付产品族和跨平台 UI 换肤两个实战案例,彻底搞懂“产品族”与“产品等级结构”,并给出与工厂方法的终极选型指南。
📖《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:工厂模式 |当前:抽象工厂模式| 下一篇:建造者模式
🔗 返回系列总目录
1. 从一个“换肤”需求说起
假设你正在开发一套跨平台的 UI 组件库,需要同时支持 Windows 风格和 Mac 风格。每个风格的组件都是一整套的:按钮、文本框、下拉框…… 如果直接用new创建:
Buttonbtn=newWinButton();TextFieldtf=newWinTextField();当需要切换到 Mac 风格时,你必须把每一个new的地方都改成new MacButton()、new MacTextField()。这不仅繁琐,还容易漏改,导致界面风格“串味”——变成一锅粥。
更麻烦的是,如果将来要新增一个 Linux 风格,所有创建代码都得再改一遍。
抽象工厂模式正是为了解决这类“产品族”创建问题而生的:它把一族相关的产品交给一个专门的工厂来统一生产,切换工厂就等于切换整个产品族,保证风格绝对一致。
2. 模式定义与核心概念
抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。它属于创建型设计模式。
2.1 两个关键概念
- 产品等级结构:即产品的继承结构。例如
Button是一个抽象接口,WinButton和MacButton是它的具体实现。Button和TextField属于不同的产品等级结构。 - 产品族:由同一个工厂生产的、位于不同产品等级结构中的一组产品。例如
WinFactory生产的WinButton+WinTextField构成一个Windows 产品族。
2.2 一分钟选型指南
| 你的场景 | 推荐模式 | 一句话理由 |
|---|---|---|
| 单一产品类型,但需要灵活扩展 | 工厂方法 | 每新增一个产品只需加一个工厂类,符合开闭原则 |
| 多个产品类型成套出现,需要整体切换 | 抽象工厂 | 一个工厂生产一族产品,保证风格/协议一致 |
| 产品类型数量不固定,未来可能新增产品类型 | 谨慎使用抽象工厂 | 新增产品类型(如新增Checkbox)需要改动所有工厂,代价高 |
3. UML 类图
4. 代码实现:跨平台 UI 组件库(经典案例)
4.1 抽象产品
publicinterfaceButton{voidpaint();}publicinterfaceTextField{voidrender();}4.2 具体产品(Windows 风格)
publicclassWinButtonimplementsButton{@Overridepublicvoidpaint(){System.out.println("渲染 Windows 风格按钮");}}publicclassWinTextFieldimplementsTextField{@Overridepublicvoidrender(){System.out.println("渲染 Windows 风格文本框");}}4.3 具体产品(Mac 风格)
publicclassMacButtonimplementsButton{@Overridepublicvoidpaint(){System.out.println("渲染 Mac 风格按钮");}}publicclassMacTextFieldimplementsTextField{@Overridepublicvoidrender(){System.out.println("渲染 Mac 风格文本框");}}4.4 抽象工厂
publicinterfaceGUIFactory{ButtoncreateButton();TextFieldcreateTextField();}4.5 具体工厂
publicclassWinFactoryimplementsGUIFactory{@OverridepublicButtoncreateButton(){returnnewWinButton();}@OverridepublicTextFieldcreateTextField(){returnnewWinTextField();}}publicclassMacFactoryimplementsGUIFactory{@OverridepublicButtoncreateButton(){returnnewMacButton();}@OverridepublicTextFieldcreateTextField(){returnnewMacTextField();}}✅ 切换工厂只需改动一行代码,整个产品族同步切换,彻底杜绝“串味”风险。
4.6 客户端
GUIFactoryfactory=newWinFactory();Buttonbtn=factory.createButton();TextFieldtf=factory.createTextField();btn.paint();// Windows 风格按钮tf.render();// Windows 风格文本框// 一键换肤factory=newMacFactory();btn=factory.createButton();tf=factory.createTextField();5. 代码实现:支付产品族(支付 + 收款)
5.1 抽象产品
publicinterfaceIPay{voidpay();}publicinterfaceICollect{voidcollect();}5.2 具体产品(阿里系)
publicclassAliPayimplementsIPay{publicvoidpay(){System.out.println("【支付宝】支付成功");}}publicclassAliCollectimplementsICollect{publicvoidcollect(){System.out.println("【支付宝】收款到账");}}5.3 具体产品(微信系)
publicclassWxPayimplementsIPay{publicvoidpay(){System.out.println("【微信】支付成功");}}publicclassWxCollectimplementsICollect{publicvoidcollect(){System.out.println("【微信】收款到账");}}5.4 抽象工厂
publicabstractclassPayFactory{publicvoidinit(){System.out.println("初始化支付环境...");}publicabstractIPaycreatePay();publicabstractICollectcreateCollect();}5.5 具体工厂
publicclassAliFactoryextendsPayFactory{publicIPaycreatePay(){returnnewAliPay();}publicICollectcreateCollect(){returnnewAliCollect();}}publicclassWxFactoryextendsPayFactory{publicIPaycreatePay(){returnnewWxPay();}publicICollectcreateCollect(){returnnewWxCollect();}}✅ 同一个工厂保证了支付和收款使用同一渠道,客户端只与抽象工厂交互,扩展新渠道只需新增一个工厂类。
5.6 客户端
PayFactoryfactory=newAliFactory();factory.init();factory.createPay().pay();factory.createCollect().collect();factory=newWxFactory();factory.init();factory.createPay().pay();6. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 保证产品族一致性:同一工厂的所有产品风格/协议天然兼容 | 新增产品等级困难:若要新增一个产品类型(如Checkbox),抽象工厂及所有具体工厂都要修改 |
| 切换产品族极其简单:只需更换工厂实例 | 类数量增加,系统复杂 |
| 客户端与具体产品解耦 | 仅适用于存在产品族且产品等级相对稳定的场景 |
7. 抽象工厂 vs 工厂方法
| 对比维度 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 解决问题 | 单一产品的横向扩展 | 产品族的纵向创建 |
| 工厂数量 | 每个产品一个工厂 | 每个产品族一个工厂 |
| 扩展方向 | 新增产品只需新增工厂类(符合 OCP) | 新增产品族只需新增工厂类(符合 OCP),新增产品类型需修改抽象工厂(违反 OCP) |
| 典型场景 | 单一支付方式扩展 | 支付渠道 + 收款渠道的成套创建 |
💡口诀:一个产品横向扩展用工厂方法,一族产品成套创建用抽象工厂。
8. 框架中的抽象工厂
- MyBatis 的
SqlSessionFactory:负责创建SqlSession及关联的Configuration,确保它们属于同一个数据库环境。 - JDBC 的
DataSource:提供getConnection(),连接、语句、结果集构成一套完整的产品族。 - Spring 的
AbstractBeanFactory:保证 Bean 的依赖、作用域、生命周期管理的一致性。
9. 常见误区与面试高频题
❌ 误区1:抽象工厂就是升级版的工厂方法
两者解决不同维度的问题,没有高低之分。
❌ 误区2:抽象工厂完全符合开闭原则
只在产品族维度上符合 OCP,在产品等级维度上违反 OCP。
💡 面试高频追问
- 抽象工厂如何保证产品族一致性?→ 同一工厂实例创建的所有产品都属于同一产品族。
- 什么时候该用抽象工厂而不是工厂方法?→ 当系统需要创建的对象之间存在“成套”或“风格统一”的强关联时。
- 抽象工厂的缺点?→ 新增产品类型困难,需要修改抽象工厂及所有具体工厂。
10. 六大设计原则在抽象工厂中的体现
| 设计原则 | 体现 |
|---|---|
| 单一职责(SRP) | 每个具体工厂只负责一个产品族 |
| 开闭原则(OCP) | 新增产品族无需修改现有代码(但新增产品类型需修改) |
| 里氏替换(LSP) | 所有产品族都可替换抽象工厂和抽象产品 |
| 依赖倒置(DIP) | 客户端依赖抽象,不依赖具体工厂 |
| 接口隔离(ISP) | 抽象工厂按产品族拆分接口,避免臃肿 |
| 迪米特法则(LoD) | 客户端只与抽象工厂交互,不接触具体产品 |
附录:| Abstract Factory UML 源码
@startuml skinparam backgroundColor #FEFEFE interface Button { + paint() } interface TextField { + render() } class WinButton implements Button class MacButton implements Button class WinTextField implements TextField class MacTextField implements TextField interface GUIFactory { + createButton() : Button + createTextField() : TextField } class WinFactory implements GUIFactory class MacFactory implements GUIFactory WinFactory ..> WinButton : creates WinFactory ..> WinTextField : creates MacFactory ..> MacButton : creates MacFactory ..> MacTextField : creates @enduml🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:工厂模式
- 当前:抽象工厂模式(你在这里)
- 下一篇:建造者模式 🚧 即将发布
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀下一篇:建造者模式 —— 构造器参数太多?试试链式调用!🚧 即将发布,敬请关注!
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。