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 风格。抽象工厂帮我们保证了每个平台下所有组件风格一致,不会出现「混搭」的尴尬情况。
适用场景
抽象工厂模式不是万能的,它适合以下几种情况:
- 产品族约束:系统需要保证多个产品之间的配套关系,不能混用
- 切换产品族:系统需要动态切换整套产品,比如换主题、换平台
- 产品族稳定:产品族不会频繁增加新产品类型(增加新产品类型需要修改抽象工厂接口)
抽象工厂模式结构图
下面这张图展示了抽象工厂模式的核心结构:
避坑指南
我曾经在一个项目中过度使用抽象工厂模式。当时觉得「所有对象都应该通过工厂创建」,结果抽象工厂接口变得臃肿不堪,每次新增一个产品类型都要修改所有工厂子类。
后来我总结了一个原则:如果产品族中的产品类型经常变化,就不要用抽象工厂模式。可以考虑用工厂方法 + 组合的方式替代。
总结
抽象工厂模式的核心价值在于「产品族的一致性」。它把一组相关的产品创建逻辑封装在一起,客户端只需要关心抽象接口,不需要知道具体产品是怎么创建的。
我个人习惯在以下场景优先考虑抽象工厂:
- 跨平台组件库
- 主题切换系统
- 数据库驱动族(不同数据库的连接、命令、事务等)
记住一句话:工厂方法解决「一个产品」的创建问题,抽象工厂解决「一组产品」的创建问题。搞清楚了这一点,面试时就不会被问倒了。