引言
在 GoF 的《设计模式》中,工厂模式分为三类:简单工厂、工厂方法、抽象工厂。实际工作中,工厂方法和抽象工厂最常用。抽象工厂模式的难点,不是代码实现,而是如何找到“正确的抽象”——它必须既简单又底层,才能真正发挥作用。今天,我们直奔主题,掌握抽象工厂模式的精髓。
模式定义与底层逻辑
定义:提供一个创建相关或相互依赖对象族的接口,而无需指定它们的具体类。
- 对使用者说:只关心一组产品(如家具系列),不在意品牌和型号。
- 对创建者说:需分析共性功能,找到正确的抽象产品接口,再交由具体工厂实现。
核心思想:提炼共性抽象,隐藏具体实现。
通用 UML 与角色说明
┌──────────────────┐ ┌──────────────────┐
│ AbstractFactory │─────▶│ AbstractProductA │
│ + createA() │ └──────────────────┘
│ + createB() │─┐
└──────────────────┘ │ ┌──────────────────┐
└────▶│ AbstractProductB │
└──────────────────┘
┌──────────────────┐ ┌──────────────────┐
│ ConcreteFactory1 │─────▶│ ConcreteProductA1│
│ + createA() │ └──────────────────┘
│ + createB() │─┐
└──────────────────┘ │ ┌──────────────────┐
└────▶│ ConcreteProductB1│
└──────────────────┘
- AbstractFactory:抽象工厂,声明创建一组产品的方法。
- ConcreteFactory:具体工厂,实现 AbstractFactory,生成一系列具体产品。
- AbstractProduct:抽象产品接口/类,定义共性功能。
- ConcreteProduct:具体产品,实现抽象产品,添加特有细节。
经典代码示例
// 抽象产品接口
public interface Chair { void sit(); }
public interface Sofa { void lie(); }
public interface Table { void use(); }
// 抽象工厂
public abstract class AbstractFactory {
abstract Chair createChair();
abstract Sofa createSofa();
abstract Table createTable();
}
// 具体工厂:中式家具
public class ChinaFactory extends AbstractFactory {
@Override public Chair createChair() { return new ChinaChair(); }
@Override public Sofa createSofa() { return new ChinaSofa(); }
@Override public Table createTable() { return new ChinaTable(); }
}
// 具体产品:中式椅子
public class ChinaChair implements Chair {
public void sit() { System.out.println("中式椅子:榉木坐享"); }
}
// …… USAFactory、USAChair 等类似
// 客户端
public class Client {
private Chair chair;
private Sofa sofa;
private Table table;
public Client(AbstractFactory f) {
chair = f.createChair();
sofa = f.createSofa();
table = f.createTable();
}
public void useAll() {
chair.sit();
sofa.lie();
table.use();
}
}
隐藏的变化与抽象产品的重要性
抽象工厂向客户端隐藏了:
- 支持的工厂集合 数目;
- 当前使用的是哪一个具体工厂;
- 被实例化的具体产品类型;
- 实例选择的依据(环境、配置等)。
而真正的核心在于找到抽象产品:只有当“椅子”“沙发”“桌子”接口足够简洁、通用,才能让具体工厂各自实现并保持一致性。
使用场景分析
- 跨平台兼容:选择不同 OS 驱动集(Windows、Mac、Linux)。
- 多产品系列:电商系统的商品、订单、物流按区域差异化实现。
- 插件/布局主题:UI 组件库按主题切换不同风格。
- JDK 案例:
DocumentBuilderFactory
、TransformerFactory
。
为什么使用抽象工厂?
- 提升复用性:同一抽象定义可复用多套具体实现(如 JDBC 驱动)。
- 解耦创建与使用:客户端只依赖抽象工厂和抽象产品,无需关心具体类。
- 增强扩展性:新增产品系列时,只需增加具体工厂及产品类,无需改动现有代码。
优势与劣势
优势
- 符合开闭原则:新增系列只需继承,无需修改抽象。
- 保证同一工厂产品族内部一致性。
- 明确单一职责:抽象工厂负责创建,产品类只关注自身功能。
- 易于新增产品系列,满足多样化需求。
劣势
- 增加代码量:接口与具体实现层级增多。
- 学习成本高:正确抽象难以初步掌握。
- 修改结构困难:若抽象产品变化,需要同步更新所有具体工厂。
总结
- 核心精髓:正确抽象——提炼简洁通用的产品接口。
- 设计要点:先分析共性,再分离具体;不要盲目抽象,否则模式流于形式。
- 实践建议:从小范围组件或跨平台需求切入,逐步掌握抽象工厂思维。