5. 抽象工厂模式:模式动机与定义、角色与结构、C++代码实现、产品族与产品等级结构

5.1 模式动机与定义

我们先从最直观的场景说起。你想想看,一个图形界面库,要支持Windows、macOS、Linux三套风格。每个系统下都有按钮、文本框、菜单这些控件。如果我用简单工厂,每加一个系统就得改工厂代码,这谁受得了?

抽象工厂模式就是为了解决这个问题而生的。它的核心思想是:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。说白了,就是让一个工厂专门生产一套“配套”的产品。比如Windows工厂只生产Windows风格的按钮和文本框,macOS工厂只生产macOS风格的。

我个人习惯把抽象工厂理解成“产品族的管家”。它不关心单个产品怎么造,它只保证你从它那里拿到的所有产品都是同一个系列的。我在项目中遇到过最典型的场景是跨平台渲染引擎——不同平台下的纹理、着色器、缓冲区管理方式完全不同,但上层逻辑只关心“创建纹理”“创建着色器”这些抽象操作。抽象工厂正好派上用场。

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

5.2 角色与结构

抽象工厂模式涉及四个核心角色,我一个个说清楚:

  • 抽象工厂(AbstractFactory):声明创建抽象产品对象的操作接口。比如声明CreateButton()、CreateTextBox()。
  • 具体工厂(ConcreteFactory):实现抽象工厂接口,负责创建具体产品对象。比如WindowsFactory、MacFactory。
  • 抽象产品(AbstractProduct):定义一类产品对象的公共接口。比如Button、TextBox。
  • 具体产品(ConcreteProduct):实现抽象产品接口的具体类。比如WindowsButton、MacButton。

这里有个关键点:产品族产品等级结构是两个容易混淆的概念。我画了张图帮你理清关系:

产品族与产品等级结构 按钮(Button) 文本框(TextBox) 菜单(Menu) WindowsButton WindowsTextBox WindowsMenu Windows
产品族 MacButton MacTextBox MacMenu macOS
产品族
LinuxButton LinuxTextBox LinuxMenu Linux
产品族
产品族(纵向) 产品等级结构(横向)

从这张图可以看得很清楚:产品等级结构是纵向的——所有按钮都属于“按钮”这个抽象类型;产品族是横向的——Windows下的所有控件属于同一个族。抽象工厂模式的核心就是保证你从同一个工厂拿到的产品属于同一个族,不会出现Windows按钮配Mac文本框这种混搭。

5.3 C++代码实现

直接上代码。我们先定义抽象产品:

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

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

然后定义具体产品:

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

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

// macOS 具体产品
class MacButton : public Button {
public:
    void paint() override {
        std::cout << "绘制macOS风格按钮" << std::endl;
    }
};

class MacTextBox : public TextBox {
public:
    void display() override {
        std::cout << "显示macOS风格文本框" << std::endl;
    }
};

接下来是抽象工厂和具体工厂:

// 抽象工厂
class GUIFactory {
public:
    virtual ~GUIFactory() = default;
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<TextBox> createTextBox() = 0;
};

// Windows 具体工厂
class WindowsFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<WindowsButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<WindowsTextBox>();
    }
};

// macOS 具体工厂
class MacFactory : public GUIFactory {
public:
    std::unique_ptr<Button> createButton() override {
        return std::make_unique<MacButton>();
    }
    std::unique_ptr<TextBox> createTextBox() override {
        return std::make_unique<MacTextBox>();
    }
};

客户端代码怎么用?看这里:

void createUI(GUIFactory& factory) {
    auto btn = factory.createButton();
    auto txt = factory.createTextBox();
    btn->paint();
    txt->display();
}

int main() {
    WindowsFactory winFactory;
    MacFactory macFactory;
    
    std::cout << "使用Windows工厂:" << std::endl;
    createUI(winFactory);
    
    std::cout << "使用macOS工厂:" << std::endl;
    createUI(macFactory);
    
    return 0;
}
个人经验:我在实际项目中喜欢用unique_ptr管理工厂对象,避免裸指针。另外,工厂本身通常设计成单例,因为一个系统只需要一个具体工厂实例。

5.4 产品族与产品等级结构

这两个概念是理解抽象工厂的关键。我再展开说说。

产品等级结构,就是产品的抽象层次。比如Button是一个抽象类,它的派生类WindowsButton、MacButton、LinuxButton构成了一个等级结构。同样,TextBox也是一个等级结构。抽象工厂模式中,每个抽象工厂可以生产多个等级结构的产品。

产品族,是指由同一个具体工厂生产的一系列产品。比如WindowsFactory生产WindowsButton、WindowsTextBox、WindowsMenu,这些产品就属于同一个产品族。它们之间往往有依赖关系或风格一致性要求。

为什么要区分这两个概念?因为在实际开发中,你经常需要切换整个产品族,而不是单个产品。比如从Windows风格切换到macOS风格,你只需要换一个工厂,所有控件风格自动统一。这就是抽象工厂模式最大的价值。

避坑指南:我曾经在一个项目中误把产品等级结构当成产品族来设计,结果工厂方法返回的是抽象产品指针,但客户端却混用了不同工厂的产品,导致界面风格错乱。记住:抽象工厂保证的是产品族内的一致性,而不是产品等级结构的扩展性。如果需要增加新的产品等级结构(比如新增一个Slider控件),你得修改所有抽象工厂接口,这违反了开闭原则。所以抽象工厂适合产品族稳定、产品等级结构相对固定的场景。

最后总结一下抽象工厂模式的适用场景:

  • 系统需要独立于产品的创建、组合和表示
  • 系统需要配置多个产品族中的某一个
  • 产品族内的产品被设计成一起使用,且需要保证这种约束
  • 你不想暴露产品类的具体实现,只暴露接口

嗯,抽象工厂模式就讲到这里。它和工厂方法模式的区别,我简单提一句:工厂方法模式针对单个产品等级结构,抽象工厂模式针对多个产品等级结构。实际项目中,我经常把两者结合使用——抽象工厂里套工厂方法,灵活性更高。


公众号:蓝海资料掘金营,微信deep3321