以"咖啡店多地区套餐系统"为例,从工厂方法出发,讲解为什么需要抽象工厂模式以及如何实现。
一、从工厂方法到抽象工厂
工厂方法的局限
在工厂方法模式中,每个地区工厂只创建一种产品(咖啡):
cpp
CoffeeAbstractFactory* factory = new ChinaCoffeeFactory();
Coffee* latte = factory->createCoffee("latte"); // 只能创建咖啡
现在需求又变了------咖啡店不仅卖咖啡,还卖甜点,而且不同地区的套餐内容不同:
| 地区 | 咖啡 | 甜点 |
|---|---|---|
| 中国区 | 拿铁 30元 | 蛋黄酥 15元 |
| 美国区 | 拿铁 $5 | 提拉米苏 $6 |
如果用工厂方法,就需要两套独立的工厂(咖啡工厂 + 甜点工厂),彼此之间没有关联,无法保证"中国区工厂一定搭配中国区甜点"。
抽象工厂的解决思路
一个工厂同时创建一组相关产品。每个地区工厂既能创建咖啡,又能创建甜点,保证产品之间的搭配关系。
工厂方法:一个工厂 → 一种产品
抽象工厂:一个工厂 → 一组相关产品(咖啡 + 甜点)
二、类图
Coffee (抽象产品A) Dessert (抽象产品B)
| |
ChinaLatte USALatte EggYolkPastry Tiramisu
\ / \ /
\ / \ /
AbstractFactory (抽象工厂)
├── createCoffee()
└── createDessert()
/ \
ChinaFactory USAFactory
创建: 创建:
ChinaLatte USALatte
EggYolkPastry Tiramisu
核心区别 :抽象工厂定义了两个 创建方法(createCoffee + createDessert),而工厂方法只有一个。
三、项目结构
include/
├── Coffee.h # 抽象产品A:咖啡基类
├── ChinaLatte.h # 中国区拿铁(复用工厂方法的类)
├── USALatte.h # 美国区拿铁(复用工厂方法的类)
├── Dessert.h # 抽象产品B:甜点基类(新增)
├── EggYolkPastry.h # 蛋黄酥(新增)
├── Tiramisu.h # 提拉米苏(新增)
├── AbstractFactory.h # 抽象工厂基类(新增)
├── ChinaFactory.h # 中国区工厂(新增)
└── USAFactory.h # 美国区工厂(新增)
src/
├── main.cpp
├── ChinaLatte.cpp # 复用
├── USALatte.cpp # 复用
├── EggYolkPastry.cpp # 新增
├── Tiramisu.cpp # 新增
├── ChinaFactory.cpp # 新增
└── USAFactory.cpp # 新增
四、逐步实现
第 1 步:定义第二种抽象产品 --- Dessert.h
咖啡基类 Coffee 已经有了,现在新增甜点基类,结构完全对称:
cpp
// include/Dessert.h
#ifndef DESSERT_H
#define DESSERT_H
#include<string>
using namespace std;
class Dessert
{
public:
virtual string getName()=0;
virtual double getPrice()=0;
virtual ~Dessert(){}
};
#endif
踩坑记录 :文件名拼写错误(
Deesert.h多了个 e),但#include写的是"Dessert.h",编译时报找不到文件。文件名一定要和 include 里的一致。
第 2 步:实现具体甜点
EggYolkPastry.h --- 蛋黄酥(声明)
cpp
#ifndef EGG_YOLK_PASTRY_H
#define EGG_YOLK_PASTRY_H
#include"Dessert.h"
class EggYolkPastry:public Dessert{
public:
string getName() override;
double getPrice() override;
};
#endif
EggYolkPastry.cpp --- 蛋黄酥(实现)
cpp
#include"EggYolkPastry.h"
string EggYolkPastry::getName(){
return "EggYolkPastry";
}
double EggYolkPastry::getPrice(){
return 15; // 中国区甜点:15元
}
Tiramisu.h --- 提拉米苏(声明)
cpp
#ifndef TIRAMISU_H
#define TIRAMISU_H
#include"Dessert.h"
class Tiramisu:public Dessert{
public:
string getName() override;
double getPrice() override;
};
#endif
Tiramisu.cpp --- 提拉米苏(实现)
cpp
#include"Tiramisu.h"
string Tiramisu::getName(){
return "Tiramisu";
}
double Tiramisu::getPrice(){
return 6; // 美国区甜点:$6
}
第 3 步:定义抽象工厂基类 --- AbstractFactory.h
这是抽象工厂模式的核心------工厂接口中定义多个创建方法:
cpp
// include/AbstractFactory.h
#ifndef ABSTRACT_FACTORY_H
#define ABSTRACT_FACTORY_H
#include"Coffee.h"
#include"Dessert.h"
class AbstractFactory{
public:
virtual Coffee* createCoffee(string type)=0; // 创建咖啡
virtual Dessert* createDessert(string type)=0; // 创建甜点
virtual ~AbstractFactory(){}
};
#endif
对比工厂方法:
工厂方法的 CoffeeAbstractFactory |
抽象工厂的 AbstractFactory |
|---|---|
createCoffee() 一个方法 |
createCoffee() + createDessert() 两个方法 |
| 只能创建咖啡 | 同时创建咖啡和甜点 |
第 4 步:实现具体工厂
ChinaFactory.h --- 中国区工厂(声明)
cpp
#ifndef CHINA_FACTORY_H
#define CHINA_FACTORY_H
#include"AbstractFactory.h"
class ChinaFactory:public AbstractFactory{
public:
Coffee* createCoffee(string type) override;
Dessert* createDessert(string type) override;
};
#endif
ChinaFactory.cpp --- 中国区工厂(实现)
cpp
#include"ChinaFactory.h"
#include"ChinaLatte.h"
#include"EggYolkPastry.h"
Coffee* ChinaFactory::createCoffee(string type){
if(type=="latte") return new ChinaLatte();
return nullptr;
}
Dessert* ChinaFactory::createDessert(string type){
if(type=="eggyolkpastry") return new EggYolkPastry();
return nullptr;
}
USAFactory.h --- 美国区工厂(声明)
cpp
#ifndef USA_FACTORY_H
#define USA_FACTORY_H
#include"AbstractFactory.h"
class USAFactory:public AbstractFactory{
public:
Coffee* createCoffee(string type) override;
Dessert* createDessert(string type) override;
};
#endif
USAFactory.cpp --- 美国区工厂(实现)
cpp
#include"USAFactory.h"
#include"USALatte.h"
#include"Tiramisu.h"
Coffee* USAFactory::createCoffee(string type){
if(type=="latte") return new USALatte();
return nullptr;
}
Dessert* USAFactory::createDessert(string type){
if(type=="tiramisu") return new Tiramisu();
return nullptr;
}
第 5 步:客户端使用
cpp
// src/main.cpp
#include<iostream>
#include<cstdlib>
#include"ChinaFactory.h"
#include"USAFactory.h"
using namespace std;
int main()
{
// 中国区工厂 → 同时创建咖啡和甜点
AbstractFactory* chinaFactory=new ChinaFactory();
Coffee* chinaCoffee=chinaFactory->createCoffee("latte");
Dessert* chinaDessert=chinaFactory->createDessert("eggyolkpastry");
cout<<"=== China Combo ==="<<endl;
cout<<"Coffee: "<<chinaCoffee->getName()<<" - "<<chinaCoffee->getPrice()<<"yuan"<<endl;
cout<<"Dessert: "<<chinaDessert->getName()<<" - "<<chinaDessert->getPrice()<<"yuan"<<endl;
cout<<"Total: "<<chinaCoffee->getPrice()+chinaDessert->getPrice()<<"yuan"<<endl;
// 美国区工厂 → 同时创建咖啡和甜点
AbstractFactory* usaFactory=new USAFactory();
Coffee* usaCoffee=usaFactory->createCoffee("latte");
Dessert* usaDessert=usaFactory->createDessert("tiramisu");
cout<<"=== USA Combo ==="<<endl;
cout<<"Coffee: "<<usaCoffee->getName()<<" - $"<<usaCoffee->getPrice()<<endl;
cout<<"Dessert: "<<usaDessert->getName()<<" - $"<<usaDessert->getPrice()<<endl;
cout<<"Total: $"<<usaCoffee->getPrice()+usaDessert->getPrice()<<endl;
delete chinaCoffee; delete chinaDessert; delete chinaFactory;
delete usaCoffee; delete usaDessert; delete usaFactory;
system("pause");
return 0;
}
运行结果:
=== China Combo ===
Coffee: China Latte - 30yuan
Dessert: EggYolkPastry - 15yuan
Total: 45yuan
=== USA Combo ===
Coffee: USA Latte - $5
Dessert: Tiramisu - $6
Total: $11
五、三种工厂模式完整对比
| 简单工厂 | 工厂方法 | 抽象工厂 | |
|---|---|---|---|
| 工厂数量 | 1 个 | 每个变体 1 个 | 每个变体 1 个 |
| 产品种类 | 1 种 | 1 种 | 多种(一组) |
| 创建方式 | static + if/else |
继承 + 多态 | 继承 + 多态 |
| 新增产品变体 | 改工厂 if/else | 加工厂子类 | 加工厂子类 |
| 新增产品种类 | --- | --- | 改抽象工厂接口(所有子工厂都要改) |
| 核心价值 | 集中创建逻辑 | 对扩展开放 | 保证产品族搭配一致 |
什么时候用哪个?
产品少、不怎么变 → 简单工厂
产品有多种变体、经常扩展 → 工厂方法
多种产品需要配套使用 → 抽象工厂
六、抽象工厂的优缺点
优点
- 保证产品族一致性:中国区工厂一定创建中国区咖啡 + 中国区甜点,不会出现混搭
- 新增地区方便 :加一个
JapanFactory,不改现有代码 - 客户端只依赖抽象接口 :
AbstractFactory*、Coffee*、Dessert*
缺点
- 新增产品种类困难 :如果要加"小食(Snack)",需要改
AbstractFactory接口加createSnack(),所有已有工厂子类都要跟着改------违反开闭原则 - 类的数量多:每个地区 × 每种产品 = 大量类文件
取舍原则
- 如果**经常新增地区(产品族变体)**→ 用抽象工厂,因为只需加工厂子类
- 如果经常新增产品种类→ 不适合抽象工厂,考虑其他模式
七、角色总结
| 角色 | 本例中 | 职责 |
|---|---|---|
| 抽象产品 A | Coffee |
咖啡的公共接口 |
| 抽象产品 B | Dessert |
甜点的公共接口 |
| 具体产品 | ChinaLatte, EggYolkPastry, USALatte, Tiramisu |
各地区的具体产品 |
| 抽象工厂 | AbstractFactory |
定义创建咖啡+甜点的接口 |
| 具体工厂 | ChinaFactory, USAFactory |
各地区工厂,创建本地区的产品组合 |
| 客户端 | main() |
选择工厂,获取配套产品 |
八、学习路径回顾
简单工厂 → 工厂方法 → 抽象工厂
| | |
1个工厂 N个工厂 N个工厂
1种产品 1种产品 多种产品
if/else 多态 多态
集中管理 易扩展变体 保证搭配一致
三种模式是递进关系,每一种都是为了解决前一种的局限而出现的。理解了这个演进过程,就能在实际项目中根据需求选择合适的模式。