Skip to content

抽象工厂模式 (Abstract Factory) —— 品牌全家桶

前言

在深入探讨抽象工厂模式之前,想向大家推荐一个非常棒的开源项目:design-patterns-23。这个项目用最现代的技术栈重写了 23 种设计模式,非常适合实战学习,还配套了在线交互演示站,可以边读文章边动手玩。本文的实战案例灵感也来源于此。

1. 抽象工厂模式是什么?

抽象工厂模式(Abstract Factory Pattern)提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

通俗解释

  • 工厂方法:好比只生产鼠标的工厂。有罗技鼠标工厂、雷蛇鼠标工厂。
  • 抽象工厂:好比品牌全家桶。罗技工厂不仅生产鼠标,还生产键盘、耳机。你买了一套“罗技全家桶”,里面的鼠标、键盘、耳机风格是统一的,驱动也是通用的。你不能在罗技工厂里买到雷蛇的键盘。

1.1 为什么我们需要它?

核心痛点产品族 (Product Family) 的约束。 在 GUI 开发中,我们有 Windows 风格和 Mac 风格。

  • Windows 风格:方形按钮 + 浅色文本框。
  • Mac 风格:圆角按钮 + 深色文本框。 如果我们不加限制,程序员可能会写出“Mac 圆角按钮 + Windows 浅色文本框”的怪胎界面。抽象工厂模式强制你必须“成套”创建产品。

1.2 产品等级结构 vs 产品族

这是理解抽象工厂的关键:

  • 产品等级结构 (Product Hierarchy):产品的继承结构。例如:抽象按钮 -> Windows按钮、Mac按钮。
  • 产品族 (Product Family):由同一个工厂生产的,位于不同产品等级结构中的一组产品。例如:Windows工厂生产的 Windows按钮、Windows文本框。

2. Java 实战演练:跨平台 UI 组件库

我们要开发一个 UI 库,支持 Windows 和 Mac 两种风格。每种风格都有 Button(按钮)和 Checkbox(复选框)。

2.1 定义抽象产品

按钮接口

java
public interface Button {
    void paint();
}

复选框接口

java
public interface Checkbox {
    void paint();
}

2.2 定义具体产品 (Windows 系列)

java
public class WindowsButton implements Button {
    @Override
    public void paint() {
        System.out.println("渲染 Windows 风格的方形按钮");
    }
}

public class WindowsCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("渲染 Windows 风格的复选框");
    }
}

2.3 定义具体产品 (Mac 系列)

java
public class MacButton implements Button {
    @Override
    public void paint() {
        System.out.println("渲染 Mac 风格的圆角按钮");
    }
}

public class MacCheckbox implements Checkbox {
    @Override
    public void paint() {
        System.out.println("渲染 Mac 风格的复选框");
    }
}

2.4 定义抽象工厂接口

注意:这里有两个方法,分别创建 Button 和 Checkbox。这就是产品族

java
public interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

2.5 定义具体工厂

Windows 工厂:只生产 Windows 系列产品。

java
public class WindowsFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

Mac 工厂:只生产 Mac 系列产品。

java
public class MacFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacCheckbox();
    }
}

2.6 客户端调用

客户端只知道 GUIFactory,不知道是 Windows 还是 Mac。

java
public class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

public class Demo {
    public static void main(String[] args) {
        // 模拟配置读取
        String osName = System.getProperty("os.name").toLowerCase();
        GUIFactory factory;
        
        if (osName.contains("mac")) {
            factory = new MacFactory();
        } else {
            factory = new WindowsFactory();
        }
        
        Application app = new Application(factory);
        app.paint();
    }
}

3. 核心难点:工厂方法 vs 抽象工厂

这也是面试必问。

特性工厂方法模式 (Factory Method)抽象工厂模式 (Abstract Factory)
关注点一个产品等级结构多个产品等级结构 (产品族)
创建对象数量一个工厂方法只创建一个产品一个抽象工厂包含多个工厂方法,创建一组产品
复杂度较低较高
典型场景日志记录器 (文件/数据库)换肤 (一套皮肤包含按钮、背景、字体等)
开闭原则增加新产品符合开闭原则增加新产品族符合开闭原则,但增加新产品等级(如增加 Textbox)违反开闭原则

详细解释“开闭原则”的悖论

  • 增加新族 (Easy):如果我想增加 Linux 风格,只需新建 LinuxFactoryLinuxButtonLinuxCheckbox。现有代码不用改。符合开闭原则。
  • 增加新产品 (Hard):如果我想给每种风格都增加一个 Textbox,我必须:
    1. 修改 GUIFactory 接口,增加 createTextbox()
    2. 修改 WindowsFactoryMacFactory 等所有实现类。 这严重违反了开闭原则!所以,抽象工厂模式适合产品族比较稳定的场景

4. 源码中的抽象工厂模式

  1. java.sql.Connection: 这是最经典的例子。 Connection 接口就像是一个抽象工厂,它能创建 StatementPreparedStatementCallableStatement 等一系列相关的对象。

    java
    public interface Connection  {
        Statement createStatement() throws SQLException;
        PreparedStatement prepareStatement(String sql) throws SQLException;
        CallableStatement prepareCall(String sql) throws SQLException;
    }

    不同的数据库驱动(MySQL, Oracle)提供了 Connection 的不同实现,生产出对应的 Statement 对象。

  2. javax.xml.parsers.DocumentBuilderFactory: 用于创建 XML 解析器。

  3. Spring 的 BeanFactory: 虽然名字叫 Factory,但它更像是一个容器。不过,ApplicationContext 可以看作是一个复杂的抽象工厂,它负责创建和管理整个应用中的所有 Bean。

5. 优缺点与适用场景总结

优点

  • 保证一致性:强制客户端始终使用同一产品族中的对象。
  • 解耦:客户端代码与具体产品的创建逻辑解耦。

缺点

  • 扩展新产品困难:难以支持新种类的产品。如果要增加一种新产品,需要修改抽象工厂接口及其所有子类。

适用场景

  1. 系统要独立于它的产品的创建、组合和表示时。

6. 实验实操

design-patterns-web 项目中,左侧模拟了没有使用抽象工厂的混乱场景,点击创建产品时,系统随机分配 Apple 或 Samsung 生态,导致产品风格不统一(如 Apple 手机搭配 Samsung 手表)。 右侧展示了抽象工厂模式,首先选择“Apple”或“Samsung”作为当前的工厂家族。随后点击创建 Phone、Laptop 或 Watch 时,工厂保证产出的所有设备都属于同一个生态系统,确保了产品族的一致性。 截图建议:截取右侧选择了某一特定生态(如 Apple)后,创建的一系列风格统一的产品(iPhone, MacBook, Apple Watch)。 2. 系统要由多个产品系列中的一个来配置时(如换肤、多数据库支持)。 3. 要强调一系列相关的产品对象的设计以便进行联合使用时。 4. 提供一个产品类库,而只想显示它们的接口而不是实现时。

6. 结语

抽象工厂模式是工厂模式的终极形态。它解决了“成套匹配”的问题,但也引入了“难以新增产品种类”的刚性约束。在架构设计时,如果你预见到产品种类(Button, Checkbox)非常稳定,但产品风格(Win, Mac, Linux)经常变化,那么抽象工厂是你的不二之选。