抽象工厂模式:创建一系列相关对象,无需指定具体类

抽象工厂模式,说白了就是「工厂的工厂」。

我刚开始接触设计模式时,觉得工厂方法已经够用了。直到有一次,我在做一个跨平台UI框架的项目——Windows、macOS、Linux三套界面风格,每个平台都有按钮、输入框、菜单这些组件。如果用工厂方法,我得为每个组件写三个工厂类,那代码量……嗯,你想想看,光是按钮就有WindowsButtonFactory、MacButtonFactory、LinuxButtonFactory,再来个输入框……简直灾难。

抽象工厂就是来解决这个问题的。它让你创建一组「配套」的对象,而且客户端代码完全不用关心具体是哪个平台。

核心思想

抽象工厂模式的定义其实很直白:

提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。

关键点在于「一系列」和「相关」。不是随便什么对象都能塞进一个抽象工厂里,它们必须属于同一个「产品族」。

举个例子:

  • Windows风格:蓝色按钮、圆角输入框、扁平菜单
  • macOS风格:灰色按钮、无边框输入框、圆角菜单
  • Linux风格:橙色按钮、方形输入框、经典菜单

这三组组件,每组内部是「配套」的。你不能把Windows的按钮和macOS的输入框混在一起用,那界面就乱套了。

结构图

先看一张图,把整体结构理清楚:

抽象工厂模式结构图 «interface» AbstractFactory +createProductA() +createProductB() ConcreteFactory1 +createProductA() +createProductB() ConcreteFactory2 +createProductA() +createProductB() «interface» AbstractProductA +operationA() ProductA1 +operationA() ProductA2 +operationA() «interface» AbstractProductB +operationB() ProductB1 +operationB() ProductB2 +operationB() Client 使用 AbstractFactory 接口 创建关系 实现/继承

这张图里,AbstractFactory 定义了两个创建方法:createProductA()createProductB()。每个具体工厂(ConcreteFactory1、ConcreteFactory2)负责生产一整套产品。客户端只跟抽象工厂和抽象产品打交道,具体是哪个平台的组件,它根本不知道。

什么时候用?

我个人习惯,遇到以下场景就会考虑抽象工厂:

  • 系统需要独立于产品的创建、组合和表示——说白了,客户端不想知道「这东西怎么造出来的」。
  • 系统要配置多个产品族中的某一个——比如换皮肤、换数据库、换UI风格。
  • 产品族内的对象必须一起使用——就像前面说的,Windows按钮配Windows输入框,不能混搭。
  • 你想提供一组产品类库,只暴露接口不暴露实现——嗯,这也是封装变化的好办法。

代码示例

来看一个实际例子。假设我们在做一个跨平台的界面库:

// 抽象产品:按钮
interface Button {
    void render();
}

// 抽象产品:输入框
interface Input {
    void render();
}

// 具体产品:Windows按钮
class WindowsButton implements Button {
    public void render() {
        System.out.println("渲染Windows风格按钮 - 蓝色、直角");
    }
}

// 具体产品:Windows输入框
class WindowsInput implements Input {
    public void render() {
        System.out.println("渲染Windows风格输入框 - 带边框、白色背景");
    }
}

// 具体产品:macOS按钮
class MacButton implements Button {
    public void render() {
        System.out.println("渲染macOS风格按钮 - 灰色、圆角");
    }
}

// 具体产品:macOS输入框
class MacInput implements Input {
    public void render() {
        System.out.println("渲染macOS风格输入框 - 无边框、毛玻璃效果");
    }
}

// 抽象工厂
interface UIFactory {
    Button createButton();
    Input createInput();
}

// 具体工厂:Windows工厂
class WindowsFactory implements UIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    public Input createInput() {
        return new WindowsInput();
    }
}

// 具体工厂:macOS工厂
class MacFactory implements UIFactory {
    public Button createButton() {
        return new MacButton();
    }
    public Input createInput() {
        return new MacInput();
    }
}

// 客户端代码
class Application {
    private Button button;
    private Input input;

    public Application(UIFactory factory) {
        button = factory.createButton();
        input = factory.createInput();
    }

    public void render() {
        button.render();
        input.render();
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        // 运行时决定用哪个工厂
        String os = System.getProperty("os.name").toLowerCase();
        UIFactory factory;
        if (os.contains("win")) {
            factory = new WindowsFactory();
        } else {
            factory = new MacFactory();
        }

        Application app = new Application(factory);
        app.render();
    }
}

你看,客户端 Application 完全不知道它用的是 Windows 还是 macOS 的组件。它只认 UIFactory 接口。这就是抽象工厂的威力——把「产品族」的创建逻辑集中管理,客户端代码干干净净。

优缺点分析

优点 缺点
隔离了具体类,客户端只依赖抽象接口 增加新产品族困难——每加一个产品族就要加一套具体工厂
保证产品族内对象的一致性 支持新产品类型困难——如果要在所有工厂里加一个「滑块」组件,所有具体工厂都得改
符合开闭原则——增加新工厂不影响现有代码 代码结构比工厂方法更复杂,小项目慎用

注意:抽象工厂不是银弹。如果你的产品族经常变化,或者产品类型经常增加,抽象工厂会让你很痛苦。我曾经在一个项目中,产品类型从3个扩展到8个,每个具体工厂都要加5个新方法……那段时间改代码改到想吐。

避坑指南

我踩过不少坑,分享几个给你:

  • 别滥用抽象工厂。如果你的系统只有一组产品,或者产品之间没有「配套」关系,用简单工厂或者工厂方法就够了。抽象工厂的复杂度不是白给的。
  • 产品族要稳定。如果你不确定未来会不会加新产品类型,可以考虑用「参数化工厂方法」——在创建方法里加个类型参数,但这样会牺牲一些类型安全。
  • 注意工厂的粒度。一个抽象工厂不要管太多产品类型。我见过有人把数据库连接、日志、缓存、消息队列全塞到一个抽象工厂里……那玩意儿比意大利面还乱。

我的习惯:抽象工厂通常配合「依赖注入」一起用。在Spring里,你只需要配置不同的Bean,就能切换整个产品族。比如开发环境用Mock工厂,生产环境用真实工厂,测试环境用Stub工厂——切换起来特别方便。

与工厂方法的区别

很多人搞混这两个模式。我简单说一下:

  • 工厂方法:一个工厂只生产一种产品。你要生产按钮,就写一个按钮工厂;要生产输入框,再写一个输入框工厂。
  • 抽象工厂:一个工厂生产一整套产品。你要Windows风格,一个Windows工厂就能生产按钮、输入框、菜单……所有东西。

说白了,工厂方法是「单一产品」的创建,抽象工厂是「产品族」的创建。抽象工厂内部往往用工厂方法来实现每个产品的创建。

抽象工厂模式,是我在跨平台开发、主题切换、多数据库支持等场景下的首选。它让代码的扩展性好了不止一个档次。但记住,别为了用模式而用模式——如果你的项目只有一套UI,写个简单工厂就够了,别整抽象工厂那一套。

嗯,今天就聊到这儿。抽象工厂的核心就是「配套」二字,你记住这个,就不会用错。