抽象工厂模式实战:用C++打造家具生产系统(附UML图与完整代码)
大家好,今天我们深入探讨设计模式中的"抽象工厂模式"。在面向对象设计中,"封装对象创建逻辑"是降低耦合、提升扩展性的核心思路,而抽象工厂模式正是针对"一系列相关或依赖对象创建"场景的经典解决方案。本文将以"家具生产"为生活案例,结合完整C++代码与UML图,带你从理论到实践,吃透抽象工厂模式的设计逻辑与落地方法。
一、抽象工厂模式的核心定义与生活案例
抽象工厂模式的核心思想是:提供一个接口,用于创建一系列相互关联或依赖的对象,而无需指定它们具体的类。
生活中最典型的场景就是"家具套装选购":我们装修时,通常会选择统一风格的家具------比如"现代风格套装"包含现代沙发、现代椅子,"古典风格套装"包含古典沙发、古典椅子。很少会把现代沙发和古典椅子混搭,因为这会破坏风格统一性。
这里的"现代风格""古典风格"就是产品族 (一系列相关对象),"沙发""椅子"则是产品等级结构(同一类产品的不同实现)。抽象工厂模式的本质,就是将同一产品族的创建逻辑封装到专属工厂中,让客户端能快速获取风格一致的产品,同时彻底屏蔽具体产品的创建细节。
二、抽象工厂模式的核心角色(附UML类图)
抽象工厂模式包含5个核心角色,各角色职责清晰、分工明确。我们结合"家具生产"场景与代码,先通过UML图理清角色间的协作关系:
1. 抽象工厂模式UML类图(家具生产场景)

2. 核心角色解析(对应代码与场景)
| 角色名称 | 代码中的类/对象 | 核心职责 |
|---|---|---|
| 抽象工厂(Abstract Factory) | Factory类 |
定义创建同一产品族所有产品的接口,如createSofa()用于创建沙发,createChair()用于创建椅子,约束具体工厂的实现规范 |
| 具体工厂(Concrete Factory) | ModernFactory/ClassicalFactory类 |
实现抽象工厂接口,负责创建某一产品族的具体产品------比如ModernFactory专门生产现代风格的沙发和椅子,ClassicalFactory专门生产古典风格的家具 |
| 抽象产品(Abstract Product) | Sofa/Chair类 |
定义某一产品等级结构的统一接口,是所有具体产品的基类------Sofa是所有沙发的抽象,Chair是所有椅子的抽象 |
| 具体产品(Concrete Product) | ModernSofa/ClassicalChair等类 |
实现抽象产品接口,是抽象工厂最终创建的实例,如ModernSofa是现代沙发的具体实现,ClassicalChair是古典椅子的具体实现 |
| 客户端(Client) | main函数 |
通过抽象工厂接口获取产品,无需关心具体工厂和产品的实现细节------只需输入"modern"或"classical",就能获取对应风格的家具套装 |
三、不使用抽象工厂模式的问题:传统实现与工厂方法的局限
在处理多产品族、多产品等级的场景时,不使用抽象工厂模式会面临明显缺陷。我们通过对比传统硬编码和工厂方法模式,来明确抽象工厂模式的必要性。
1. 传统硬编码实现的痛点
直接在客户端创建具体产品,是最直观但最不可取的方式:
cpp
int main() {
vector<unique_ptr<Sofa>> sofas;
vector<unique_ptr<Chair>> chairs;
// 客户端需手动判断风格,逐一创建产品
string style = "modern";
if (style == "modern") {
sofas.push_back(make_unique<ModernSofa>());
chairs.push_back(make_unique<ModernChair>());
} else {
sofas.push_back(make_unique<ClassicalSofa>());
chairs.push_back(make_unique<ClassicalChair>());
}
return 0;
}
这种方式的问题显而易见:
- 风格一致性无法保证 :客户端可能误将
ModernSofa与ClassicalChair搭配; - 耦合度极高:客户端直接依赖所有具体产品类,产品类的任何修改都会影响客户端;
- 扩展性为零:新增风格或产品类型时,需大面积修改客户端代码。
2. 工厂方法模式的局限
工厂方法模式通过"一个工厂对应一个产品"的方式解耦了客户端与具体产品,但在处理产品族时仍有局限:
cpp
// 工厂方法模式:为每个产品创建专属工厂
class SofaFactory {
public:
virtual Sofa* createSofa() = 0;
};
class ModernSofaFactory : public SofaFactory {
public:
Sofa* createSofa() override { return new ModernSofa; }
};
class ClassicalSofaFactory : public SofaFactory {
public:
Sofa* createSofa() override { return new ClassicalSofa; }
};
// 椅子工厂类似,此处省略...
// 客户端使用
int main() {
// 创建现代风格家具需同时操作两个工厂
SofaFactory* sofaFactory = new ModernSofaFactory;
ChairFactory* chairFactory = new ModernChairFactory;
sofaFactory->createSofa();
chairFactory->createChair();
// ...
}
工厂方法模式的问题在于:
- 无法保证产品族一致性 :客户端需手动协调
SofaFactory和ChairFactory的风格,仍可能出现混搭; - 工厂类数量爆炸:每增加一个产品类型或风格,需新增大量工厂类(如4种产品×2种风格=8个工厂);
- 客户端逻辑复杂:创建一套家具需操作多个工厂,增加使用成本。
3. 抽象工厂模式的优势对比
抽象工厂模式通过"一个工厂对应一个产品族"的设计,完美解决了上述问题:
- 天然保证风格一致 :
ModernFactory只生产现代风格产品,避免混搭; - 工厂数量精简:N种风格只需N个工厂,而非N×M个(M为产品类型);
- 客户端逻辑简化:获取一套家具只需操作一个工厂,屏蔽多产品创建的复杂性。
四、代码实战:抽象工厂模式实现家具生产系统
你的代码完美落地了抽象工厂模式的核心逻辑,我们逐部分解析,看它如何解决传统实现的痛点:
1. 步骤1:定义抽象产品与具体产品(构建产品等级结构)
首先创建Sofa和Chair两个抽象产品类,再实现不同风格的具体产品,明确产品等级结构的接口与实现:
cpp
#include<iostream>
#include<vector>
#include<memory>
using namespace std;
// 抽象产品1:沙发(定义沙发的统一接口)
class Sofa { };
// 具体产品1-1:现代风格沙发(实现抽象沙发接口)
class ModernSofa:public Sofa {
public:
ModernSofa() { cout<<"modern sofa"<<endl; }
};
// 具体产品1-2:古典风格沙发(实现抽象沙发接口)
class ClassicalSofa:public Sofa {
public:
ClassicalSofa() { cout<<"classical sofa"<<endl; }
};
// 抽象产品2:椅子(定义椅子的统一接口)
class Chair { };
// 具体产品2-1:现代风格椅子(实现抽象椅子接口)
class ModernChair:public Chair {
public:
ModernChair() { cout<<"modern chair"<<endl; }
};
// 具体产品2-2:古典风格椅子(实现抽象椅子接口)
class ClassicalChair:public Chair {
public:
ClassicalChair() { cout<<"classical chair"<<endl; }
};
2. 步骤2:定义抽象工厂与具体工厂(封装产品族创建逻辑)
抽象工厂Factory定义创建"沙发+椅子"产品族的接口,具体工厂则实现对应风格的产品创建逻辑,确保同一工厂产出的产品风格统一:
cpp
// 抽象工厂:定义创建家具产品族的接口
class Factory {
public:
// 创建沙发(对应抽象产品1)
virtual Sofa* createSofa() = 0;
// 创建椅子(对应抽象产品2)
virtual Chair* createChair() = 0;
// 虚析构:确保具体工厂对象销毁时能正确调用自身析构函数
virtual ~Factory() = default;
};
// 具体工厂1:现代风格家具工厂(生产现代产品族)
class ModernFactory:public Factory {
public:
Sofa* createSofa() override {
return new ModernSofa; // 生产现代沙发
}
Chair* createChair() override {
return new ModernChair; // 生产现代椅子
}
};
// 具体工厂2:古典风格家具工厂(生产古典产品族)
class ClassicalFactory:public Factory {
public:
Sofa* createSofa() override {
return new ClassicalSofa; // 生产古典沙发
}
Chair* createChair() override {
return new ClassicalChair; // 生产古典椅子
}
};
3. 步骤3:客户端调用(简化产品获取逻辑)
客户端只需根据需求选择具体工厂,通过抽象工厂接口获取产品,无需关心具体产品的创建细节,同时保证风格一致性:
cpp
int main() {
int number;
cin >> number;
vector<unique_ptr<Sofa>> sofas;
vector<unique_ptr<Chair>> chairs;
unique_ptr<Factory> factory; // 依赖抽象工厂,不直接依赖具体工厂
for (int i = 0; i < number; i++) {
string furnitureType;
cin >> furnitureType;
// 根据风格选择具体工厂,确保产品族统一
if (furnitureType == "modern") {
factory = make_unique<ModernFactory>();
} else {
factory = make_unique<ClassicalFactory>();
}
// 通过抽象工厂接口获取产品,屏蔽具体产品创建细节
unique_ptr<Chair> it1(factory->createChair());
unique_ptr<Sofa> it2(factory->createSofa());
chairs.push_back(move(it1));
sofas.push_back(move(it2));
}
return 0;
}
4. 代码运行示例
输入(生产2套家具,1套现代风格、1套古典风格):
2
modern
classical
输出(产品族风格统一,创建逻辑清晰):
modern chair
modern sofa
classical chair
classical sofa
五、抽象工厂模式的优缺点
1. 优点
- 保证产品族一致性:同一工厂创建的产品天然属于同一风格,避免客户端误搭配,无需手动校验风格;
- 降低耦合度:客户端仅与抽象工厂、抽象产品交互,不依赖具体工厂和具体产品,代码灵活性更高;
- 扩展性强(产品族维度) :新增产品族(如北欧风格)时,只需新增
NordicFactory和对应具体产品(NordicSofa、NordicChair),无需修改现有代码; - 便于产品族切换 :切换风格只需更换具体工厂(如从
ModernFactory改为ClassicalFactory),客户端核心逻辑无需改动。
2. 缺点
- 扩展产品等级结构复杂 :若新增产品类型(如"桌子"),需修改所有工厂类(抽象工厂
Factory、具体工厂ModernFactory等),违反"开闭原则"; - 类数量激增 :每增加一个产品族或一个产品等级,都需新增多个类------比如新增北欧风格,需额外创建
NordicFactory、NordicSofa、NordicChair三个类,增加系统理解成本。
六、抽象工厂模式的应用场景
以下场景特别适合使用抽象工厂模式:
- 需要统一产品族风格:如家具套装、电子设备套装(手机+耳机+平板同品牌)、UI组件库(按钮+输入框+下拉框同主题);
- 客户端不关心产品创建细节:如框架开发中,为用户提供"一键获取整套组件"的接口,无需用户手动创建每个组件;
- 产品族稳定,产品等级结构变化少:如家电行业(冰箱、洗衣机、空调同品牌),产品类型相对固定,新增品牌(产品族)的需求更频繁。
七、总结
抽象工厂模式的核心是"抽象工厂定规范,具体工厂产同族"。通过家具生产系统的案例,我们能清晰看到:它将产品族的创建逻辑封装成独立工厂,既解决了传统硬编码中"风格混乱""耦合度高"的问题,又弥补了工厂方法模式在处理多产品协同创建时的不足。
需要注意的是,抽象工厂模式并非万能------它更适合产品族稳定的场景。若产品等级结构频繁变化(如频繁新增家具类型),可结合"工厂方法模式"进一步优化。希望本文能帮你掌握抽象工厂模式的精髓,在实际项目中灵活运用。
本文部分设计思想与术语参考自《大话设计模式》。