C++ 工厂方法模式实战指南
以"咖啡店多地区饮品制作系统"为例,从简单工厂出发,讲解为什么需要工厂方法模式以及如何实现。
一、从简单工厂到工厂方法
简单工厂的局限
在简单工厂模式中,我们用一个 CoffeeSimpleFactory 根据字符串参数创建不同的咖啡:
cpp
Coffee* latte = CoffeeSimpleFactory::createCoffee("latte");
现在需求变了------咖啡店开到了中国和美国,同一种咖啡在不同地区价格不同。如果继续用简单工厂,就要这样改:
cpp
// 简单工厂的做法:参数越来越多,if/else 越来越长
static Coffee* createCoffee(string type, string region){
if(region=="china" && type=="latte") return new ChinaLatte();
if(region=="usa" && type=="latte") return new USALatte();
// ... 每加一个地区 x 每种咖啡 = 爆炸增长
}
问题 :每新增一个地区或一种咖啡,都要修改工厂的 if/else------违反开闭原则。
工厂方法的解决思路
用多态替代 if/else:定义一个抽象工厂基类,每个地区各自实现一个工厂子类。
简单工厂:1 个工厂 + if/else 选产品
工厂方法:N 个工厂子类,各自负责自己地区的产品
二、类图
Coffee (抽象产品)
/ | \
ChinaAmericano ChinaLatte ChinaCappuccino (中国区产品)
USAAmericano USALatte USACappuccino (美国区产品)
CoffeeAbstractFactory (抽象工厂)
/ \
ChinaCoffeeFactory USACoffeeFactory (具体工厂)
创建中国区产品 创建美国区产品
关键关系:
CoffeeAbstractFactory定义接口createCoffee()ChinaCoffeeFactory和USACoffeeFactory各自实现,创建自己地区的产品- 客户端通过基类指针
CoffeeAbstractFactory*调用,不关心具体是哪个工厂
三、项目结构
include/
├── Coffee.h # 抽象产品基类
├── ChinaAmericano.h # 中国区美式(声明)
├── ChinaLatte.h # 中国区拿铁(声明)
├── ChinaCappuccino.h # 中国区卡布奇诺(声明)
├── USAAmericano.h # 美国区美式(声明)
├── USALatte.h # 美国区拿铁(声明)
├── USACappuccino.h # 美国区卡布奇诺(声明)
├── CoffeeAbstractFactory.h # 抽象工厂基类(声明)
├── ChinaCoffeeFactory.h # 中国区工厂(声明)
└── USACoffeeFactory.h # 美国区工厂(声明)
src/
├── main.cpp # 主函数
├── ChinaAmericano.cpp # 中国区产品实现
├── ChinaLatte.cpp
├── ChinaCappuccino.cpp
├── USAAmericano.cpp # 美国区产品实现
├── USALatte.cpp
├── USACappuccino.cpp
├── ChinaCoffeeFactory.cpp # 工厂实现
└── USACoffeeFactory.cpp
四、逐步实现
第 1 步:抽象产品基类(复用简单工厂的 Coffee)
cpp
// include/Coffee.h
#ifndef COFFEE_H
#define COFFEE_H
#include<string>
using namespace std;
class Coffee
{
public:
virtual string getName()=0;
virtual double getPrice()=0;
virtual ~Coffee(){}
};
#endif
第 2 步:定义各地区的具体产品
以中国区拿铁为例,其他产品结构完全一样:
cpp
// include/ChinaLatte.h --- 声明
#ifndef CHINA_LATTE_H
#define CHINA_LATTE_H
#include"Coffee.h"
class ChinaLatte:public Coffee{
public:
string getName() override;
double getPrice() override;
};
#endif
cpp
// src/ChinaLatte.cpp --- 实现
#include"ChinaLatte.h"
string ChinaLatte::getName(){
return "China Latte";
}
double ChinaLatte::getPrice(){
return 30; // 中国区价格:30元
}
所有产品的价格对照:
| 产品 | 中国区 | 美国区 |
|---|---|---|
| Americano | 25 元 | $4 |
| Latte | 30 元 | $5 |
| Cappuccino | 32 元 | $5.5 |
第 3 步:定义抽象工厂基类
cpp
// include/CoffeeAbstractFactory.h
#ifndef COFFEE_ABSTRACT_FACTORY_H
#define COFFEE_ABSTRACT_FACTORY_H
#include"Coffee.h"
class CoffeeAbstractFactory{
public:
virtual Coffee* createCoffee(string type)=0; // 纯虚函数,子工厂必须实现
virtual ~CoffeeAbstractFactory(){}
};
#endif
注意 :抽象工厂不包含任何 if/else,它只定义"工厂应该有什么能力"。
第 4 步:实现具体工厂
中国区工厂
cpp
// include/ChinaCoffeeFactory.h --- 声明
#ifndef CHINA_COFFEE_FACTORY_H
#define CHINA_COFFEE_FACTORY_H
#include"CoffeeAbstractFactory.h"
class ChinaCoffeeFactory:public CoffeeAbstractFactory{
public:
Coffee* createCoffee(string type) override;
};
#endif
cpp
// src/ChinaCoffeeFactory.cpp --- 实现
#include"ChinaCoffeeFactory.h"
#include"ChinaAmericano.h"
#include"ChinaLatte.h"
#include"ChinaCappuccino.h"
Coffee* ChinaCoffeeFactory::createCoffee(string type){
if(type=="americano") return new ChinaAmericano();
if(type=="latte") return new ChinaLatte();
if(type=="cappuccino") return new ChinaCappuccino();
return nullptr;
}
美国区工厂
cpp
// include/USACoffeeFactory.h --- 声明
#ifndef USA_COFFEE_FACTORY_H
#define USA_COFFEE_FACTORY_H
#include"CoffeeAbstractFactory.h"
class USACoffeeFactory:public CoffeeAbstractFactory{
public:
Coffee* createCoffee(string type) override;
};
#endif
cpp
// src/USACoffeeFactory.cpp --- 实现
#include"USACoffeeFactory.h"
#include"USAAmericano.h"
#include"USALatte.h"
#include"USACappuccino.h"
Coffee* USACoffeeFactory::createCoffee(string type){
if(type=="americano") return new USAAmericano();
if(type=="latte") return new USALatte();
if(type=="cappuccino") return new USACappuccino();
return nullptr;
}
第 5 步:客户端使用
cpp
// src/main.cpp
#include<iostream>
#include<cstdlib>
#include"ChinaCoffeeFactory.h"
#include"USACoffeeFactory.h"
using namespace std;
int main()
{
// 创建中国区工厂
CoffeeAbstractFactory* chinaFactory=new ChinaCoffeeFactory();
Coffee* chinaLatte=chinaFactory->createCoffee("latte");
cout<<"=== China Factory ==="<<endl;
cout<<chinaLatte->getName()<<" - "<<chinaLatte->getPrice()<<"yuan"<<endl;
// 创建美国区工厂
CoffeeAbstractFactory* usaFactory=new USACoffeeFactory();
Coffee* usaLatte=usaFactory->createCoffee("latte");
cout<<"=== USA Factory ==="<<endl;
cout<<usaLatte->getName()<<" - $"<<usaLatte->getPrice()<<endl;
delete chinaLatte; delete usaLatte;
delete chinaFactory; delete usaFactory;
system("pause");
return 0;
}
五、关键理解
为什么 CoffeeAbstractFactory 不根据参数创建不同的工厂?
你可能会想这样写:
cpp
// 错误理解:在抽象工厂里选择创建哪个工厂
class CoffeeAbstractFactory{
static CoffeeAbstractFactory* createFactory(string region){
if(region=="china") return new ChinaCoffeeFactory();
if(region=="usa") return new USACoffeeFactory();
}
};
这又回到了简单工厂的思路------用 if/else 选择。新增地区还是要改这个 if/else。
工厂方法模式的做法是:客户端直接选择用哪个工厂,用多态来处理差异:
cpp
// 客户端决定用哪个工厂(这个选择是不可避免的)
CoffeeAbstractFactory* factory = new ChinaCoffeeFactory();
// 之后的代码完全一样,不关心具体是哪个工厂
Coffee* latte = factory->createCoffee("latte");
cout << latte->getName() << endl;
简单工厂 vs 工厂方法 对比
| 简单工厂 | 工厂方法 | |
|---|---|---|
| 工厂数量 | 1 个 | 每个变体 1 个 |
| 创建机制 | static 方法 + if/else |
继承 + virtual 多态 |
| 新增产品 | 修改工厂代码 | 新增工厂子类,不改旧代码 |
| 开闭原则 | 违反(每次都改工厂) | 遵守(只增不改) |
| 适用场景 | 产品少、变化少 | 产品族多、经常扩展 |
新增一个"日本区"需要做什么?
只需要新增文件,不修改任何现有代码:
- 新增
JapanAmericano.h/.cpp、JapanLatte.h/.cpp、JapanCappuccino.h/.cpp - 新增
JapanCoffeeFactory.h/.cpp - 在
main.cpp中使用new JapanCoffeeFactory()
没有任何现有文件被修改------这就是开闭原则。
六、常见错误
错误 1:头文件保护宏重复
cpp
// ChinaCoffeeFactory.h 中写了:
#ifndef COFFEE_SIMPLE_FACTORY_H // 错误!和 SimpleFactory 的宏重名
修复 :每个头文件的宏名必须唯一,用文件名命名:CHINA_COFFEE_FACTORY_H。
错误 2:把工厂子类写成产品类
cpp
class ChinaCoffeeFactory{
string getName(){ return "China Coffee Factory"; } // 错误!工厂不是产品
double getPrice(){ return 0; }
};
修复 :工厂类应该继承 CoffeeAbstractFactory,实现 createCoffee() 方法来创建产品。
错误 3:抽象工厂写了具体实现
cpp
class CoffeeAbstractFactory{
virtual Coffee* createCoffee(string type){
// 不应该在抽象基类里写 if/else
if(type=="latte") return new Latte();
}
};
修复 :抽象工厂用 =0 纯虚函数,实现留给子类。
七、模式角色总结
| 角色 | 本例中 | 职责 |
|---|---|---|
| 抽象产品 | Coffee |
定义产品的公共接口 |
| 具体产品 | ChinaLatte, USALatte 等 |
各地区的具体咖啡实现 |
| 抽象工厂 | CoffeeAbstractFactory |
定义创建产品的接口 |
| 具体工厂 | ChinaCoffeeFactory, USACoffeeFactory |
各地区的工厂,创建本地区产品 |
| 客户端 | main() |
选择工厂,通过基类指针使用产品 |
八、下一步
当咖啡店不仅卖咖啡,还卖甜点(多种产品族)时,就需要抽象工厂模式------每个工厂子类同时创建一组相关产品(咖啡 + 甜点),而不只是单一产品。