5. 抽象工厂模式(Abstract Factory):产品族概念,与工厂方法的区别,C++代码示例与适用场景

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

我第一次接触这个模式时,觉得它有点绕。但后来在项目中真正用上了,才发现它解决了一个很实际的问题——产品族的一致性。

什么是产品族?

先聊聊产品族这个概念。你想想看,一个系统里经常会有多个产品,它们之间是配套的。比如:

  • Windows 风格下:Windows 按钮 + Windows 文本框 + Windows 滚动条
  • Mac 风格下:Mac 按钮 + Mac 文本框 + Mac 滚动条

这里的「Windows 风格」就是一个产品族,「Mac 风格」是另一个产品族。你不能把 Windows 按钮和 Mac 文本框混在一起用,那样 UI 就乱套了。

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

与工厂方法的区别

这个问题面试里经常被问到。我简单总结一下:

对比维度 工厂方法模式 抽象工厂模式
创建对象 单个产品 多个产品(产品族)
抽象程度 一个抽象工厂类 多个抽象产品类 + 一个抽象工厂类
扩展方向 增加新产品(纵向) 增加新产品族(横向)
典型场景 日志记录器、数据库访问 跨平台 UI 组件、主题切换

嗯,这里要注意:工厂方法每次只创建一个产品,而抽象工厂一次创建一整套产品。我习惯把工厂方法比作「单件生产」,抽象工厂比作「套装生产」。

C++ 代码示例

我们写一个跨平台 UI 组件的例子。假设有按钮和文本框两种产品,Windows 和 Mac 两个产品族。

// 抽象产品:按钮
class Button {
public:
    virtual void render() = 0;
    virtual ~Button() = default;
};

// 抽象产品:文本框
class TextBox {
public:
    virtual void display() = 0;
    virtual ~TextBox() = default;
};

// 具体产品:Windows 按钮
class WindowsButton : public Button {
public:
    void render() override {
        std::cout << "渲染 Windows 风格按钮" << std::endl;
    }
};

// 具体产品:Mac 按钮
class MacButton : public Button {
public:
    void render() override {
        std::cout << "渲染 Mac 风格按钮" << std::endl;
    }
};

// 具体产品:Windows 文本框
class WindowsTextBox : public TextBox {
public:
    void display() override {
        std::cout << "显示 Windows 风格文本框" << std::endl;
    }
};

// 具体产品:Mac 文本框
class MacTextBox : public TextBox {
public:
    void display() override {
        std::cout << "显示 Mac 风格文本框" << std::endl;
    }
};

// 抽象工厂
class UIFactory {
public:
    virtual Button* createButton() = 0;
    virtual TextBox* createTextBox() = 0;
    virtual ~UIFactory() = default;
};

// 具体工厂:Windows 工厂
class WindowsFactory : public UIFactory {
public:
    Button* createButton() override {
        return new WindowsButton();
    }
    TextBox* createTextBox() override {
        return new WindowsTextBox();
    }
};

// 具体工厂:Mac 工厂
class MacFactory : public UIFactory {
public:
    Button* createButton() override {
        return new MacButton();
    }
    TextBox* createTextBox() override {
        return new MacTextBox();
    }
};

// 客户端代码
void createUI(UIFactory* factory) {
    Button* btn = factory->createButton();
    TextBox* tb = factory->createTextBox();
    btn->render();
    tb->display();
    delete btn;
    delete tb;
}

int main() {
    UIFactory* factory = new WindowsFactory();
    createUI(factory);  // 输出:渲染 Windows 风格按钮 / 显示 Windows 风格文本框
    delete factory;

    factory = new MacFactory();
    createUI(factory);  // 输出:渲染 Mac 风格按钮 / 显示 Mac 风格文本框
    delete factory;
    return 0;
}

个人经验:我在一个跨平台桌面应用项目中用过这个模式。当时要同时支持 Windows、Mac 和 Linux 三套 UI 风格。抽象工厂帮我们保证了每个平台下所有组件风格一致,不会出现「混搭」的尴尬情况。

适用场景

抽象工厂模式不是万能的,它适合以下几种情况:

  • 产品族约束:系统需要保证多个产品之间的配套关系,不能混用
  • 切换产品族:系统需要动态切换整套产品,比如换主题、换平台
  • 产品族稳定:产品族不会频繁增加新产品类型(增加新产品类型需要修改抽象工厂接口)

抽象工厂模式结构图

下面这张图展示了抽象工厂模式的核心结构:

抽象工厂 UIFactory WindowsFactory 创建 Windows 产品族 MacFactory 创建 Mac 产品族 抽象产品 A Button 抽象产品 B TextBox WindowsButton WindowsTextBox MacButton MacTextBox 客户端 通过抽象工厂接口创建产品族

避坑指南

我曾经在一个项目中过度使用抽象工厂模式。当时觉得「所有对象都应该通过工厂创建」,结果抽象工厂接口变得臃肿不堪,每次新增一个产品类型都要修改所有工厂子类。

后来我总结了一个原则:如果产品族中的产品类型经常变化,就不要用抽象工厂模式。可以考虑用工厂方法 + 组合的方式替代。

总结

抽象工厂模式的核心价值在于「产品族的一致性」。它把一组相关的产品创建逻辑封装在一起,客户端只需要关心抽象接口,不需要知道具体产品是怎么创建的。

我个人习惯在以下场景优先考虑抽象工厂:

  • 跨平台组件库
  • 主题切换系统
  • 数据库驱动族(不同数据库的连接、命令、事务等)

记住一句话:工厂方法解决「一个产品」的创建问题,抽象工厂解决「一组产品」的创建问题。搞清楚了这一点,面试时就不会被问倒了。

专注资料整理