# C++ 简单工厂模式实战指南

以"咖啡店饮品制作系统"为例,从零开始用简单工厂模式组织 C++ 代码。


一、什么是简单工厂模式

简单工厂模式的核心思想:客户端不直接 new 具体的产品类,而是通过一个工厂类来创建对象。客户端只需要告诉工厂"我要什么",工厂负责决定创建哪个具体类的实例。

没有工厂的写法(直接 new)

cpp 复制代码
Coffee* latte = new Latte();       // 客户端直接依赖 Latte 类
Coffee* americano = new Americano(); // 客户端直接依赖 Americano 类

问题:客户端需要知道所有具体类的名字,每新增一种咖啡,所有创建它的地方都要改。

使用简单工厂的写法

cpp 复制代码
Coffee* latte = CoffeeSimpleFactory::createCoffee("latte");       // 只依赖工厂
Coffee* americano = CoffeeSimpleFactory::createCoffee("americano"); // 只依赖工厂

好处 :客户端只依赖基类 Coffee 和工厂 CoffeeSimpleFactory,完全不需要知道 LatteAmericano 等具体类的存在。

类图

复制代码
         Coffee (抽象基类)
        /      |       \
  Americano   Latte   Cappuccino
        \      |       /
     CoffeeSimpleFactory
            |
          main()

二、项目结构

标准的 C++ 项目将**声明(.h)实现(.cpp)**分离:

复制代码
project/
├── include/                        # 头文件(只放声明)
│   ├── Coffee.h                    # 产品基类
│   ├── Americano.h                 # 具体产品:美式咖啡
│   ├── Latte.h                     # 具体产品:拿铁
│   ├── Cappuccino.h                # 具体产品:卡布奇诺
│   └── CoffeeSimpleFactory.h       # 简单工厂
├── src/                            # 源文件(放具体实现)
│   ├── main.cpp                    # 主函数入口
│   ├── Americano.cpp               # 美式咖啡实现
│   ├── Latte.cpp                   # 拿铁实现
│   ├── Cappuccino.cpp              # 卡布奇诺实现
│   └── CoffeeSimpleFactory.cpp     # 工厂实现
└── build/                          # 编译输出
    └── main.exe

为什么要分离 .h 和 .cpp?

文件 放什么 作用
.h 类的声明(函数签名) 告诉别人"我有什么功能"
.cpp 函数的具体实现 定义"功能怎么做"

好处:修改实现时只需重新编译对应的 .cpp,不影响其他文件。


三、逐步实现

第 1 步:定义产品基类 --- Coffee.h

cpp 复制代码
#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

要点

  • =0 表示纯虚函数,Coffee 是抽象类,不能直接实例化
  • virtual ~Coffee(){} 虚析构函数确保 delete 基类指针时能正确释放子类资源
  • #ifndef COFFEE_H / #define COFFEE_H / #endif头文件保护,防止同一文件被重复包含

第 2 步:定义具体产品类

每个具体咖啡类都继承 Coffee,分为 .h(声明)和 .cpp(实现)两个文件。

Americano.h --- 声明
cpp 复制代码
#ifndef AMERICANO_H
#define AMERICANO_H
#include"Coffee.h"

class Americano:public Coffee{
    public:
    string getName() override;      // override 表示重写基类的虚函数
    double getPrice() override;
};

#endif
Americano.cpp --- 实现
cpp 复制代码
#include"Americano.h"

string Americano::getName(){        // 类名::函数名 的写法
    return "Americano";
}

double Americano::getPrice(){
    return 30;
}

LatteCappuccino 的写法完全相同,只是返回值不同:

Latte.h
cpp 复制代码
#ifndef LATTE_H
#define LATTE_H
#include"Coffee.h"

class Latte:public Coffee{
    public:
    string getName() override;
    double getPrice() override;
};

#endif
Latte.cpp
cpp 复制代码
#include"Latte.h"

string Latte::getName(){
    return "Latte";
}

double Latte::getPrice(){
    return 35;
}
Cappuccino.h
cpp 复制代码
#ifndef CAPPUCCINO_H
#define CAPPUCCINO_H
#include"Coffee.h"

class Cappuccino:public Coffee{
    public:
    string getName() override;
    double getPrice() override;
};

#endif
Cappuccino.cpp
cpp 复制代码
#include"Cappuccino.h"

string Cappuccino::getName(){
    return "Cappuccino";
}

double Cappuccino::getPrice(){
    return 40;
}

第 3 步:定义简单工厂

CoffeeSimpleFactory.h --- 声明
cpp 复制代码
#ifndef COFFEE_SIMPLE_FACTORY_H
#define COFFEE_SIMPLE_FACTORY_H
#include"Coffee.h"

class CoffeeSimpleFactory{
    public:
    static Coffee* createCoffee(string type);   // 静态方法,不需要创建工厂实例
};

#endif

注意 :头文件中工厂只依赖 Coffee.h(基类),不需要 include 具体产品类。

CoffeeSimpleFactory.cpp --- 实现
cpp 复制代码
#include"CoffeeSimpleFactory.h"
#include"Americano.h"       // 实现中才需要知道具体类
#include"Latte.h"
#include"Cappuccino.h"

Coffee* CoffeeSimpleFactory::createCoffee(string type){
    if(type=="americano") return new Americano();
    if(type=="latte") return new Latte();
    if(type=="cappuccino") return new Cappuccino();
    return nullptr;          // 未知类型返回空指针
}

核心逻辑 :根据传入的字符串参数,用 if/else 决定创建哪个具体产品。这就是"简单工厂"的本质------一个工厂类,通过参数决定创建哪个产品

第 4 步:编写主函数

main.cpp
cpp 复制代码
#include<iostream>
#include<cstdlib>
#include"CoffeeSimpleFactory.h"     // 只需要引入工厂头文件
using namespace std;

int main()
{
    // 通过工厂创建,不直接 new 具体类
    Coffee* americano=CoffeeSimpleFactory::createCoffee("americano");
    Coffee* latte=CoffeeSimpleFactory::createCoffee("latte");
    Coffee* cappuccino=CoffeeSimpleFactory::createCoffee("cappuccino");

    // 使用产品
    cout<<americano->getName()<<" - "<<americano->getPrice()<<"元"<<endl;
    cout<<latte->getName()<<" - "<<latte->getPrice()<<"元"<<endl;
    cout<<cappuccino->getName()<<" - "<<cappuccino->getPrice()<<"元"<<endl;

    // 释放内存
    delete americano;
    delete latte;
    delete cappuccino;

    system("pause");
    return 0;
}

注意main.cpp 只 include 了 CoffeeSimpleFactory.h,完全不知道 AmericanoLatteCappuccino 的存在------这就是简单工厂的解耦效果。


四、编译和运行

多文件项目需要把所有 .cpp 一起编译:

powershell 复制代码
g++ -g src/*.cpp -I include -fexec-charset=GBK -finput-charset=UTF-8 -o build/main.exe
参数 含义
src/*.cpp 编译 src 目录下所有 .cpp 文件
-I include 告诉编译器去 include 目录找头文件
-fexec-charset=GBK 解决 Windows 控制台中文乱码
-o build/main.exe 输出到 build 目录

运行结果:

复制代码
Americano - 30元
Latte - 35元
Cappuccino - 40元

五、常见错误和踩坑记录

错误 1:变量名和类名重复

cpp 复制代码
Coffee* Americano = new Americano();  // 错误!变量名和类名都叫 Americano

报错 :编译器分不清 Americano 是变量还是类型。

修复 :变量名用小写 americano

错误 2:子类缺少 public:

cpp 复制代码
class Latte:public Coffee{
    string getName() override{...}  // 错误!默认是 private
};

报错 :外部无法调用 getName()

修复 :在成员函数前加 public:

错误 3:把简单工厂写成抽象类

cpp 复制代码
class CoffeeSimpleFactory(string type){   // 错误!类定义不能带参数
    virtual Coffee* createCoffee(...)=0;   // 错误!简单工厂不需要虚函数
};

原因:混淆了简单工厂和工厂方法模式。

修复 :简单工厂就是一个普通类 + 一个 static 方法,用 if/else 选择创建哪个产品。

错误 4:特殊字符编码报错

复制代码
error: converting to execution character set: Illegal byte sequence

原因¥ 符号在 UTF-8 转 GBK 时没有对应编码。

修复 :用中文 替代 ¥


六、简单工厂模式总结

角色分工

角色 本例中 职责
抽象产品 Coffee 定义产品接口
具体产品 Americano, Latte, Cappuccino 实现具体产品
工厂 CoffeeSimpleFactory 根据参数创建具体产品
客户端 main() 使用工厂获取产品,不关心具体实现

优点

  • 客户端与具体产品类解耦
  • 创建逻辑集中在工厂中,便于管理

缺点

  • 每新增一种产品,都要修改工厂的 if/else(违反开闭原则)
  • 工厂类职责越来越重

什么时候用

  • 产品种类不多、变化不频繁
  • 想把创建逻辑从客户端抽离出来

下一步 :当产品种类经常变化时,就需要用工厂方法模式------每种产品对应一个工厂子类,新增产品只需新增工厂子类,不用修改现有代码。

相关推荐
cany10002 小时前
C++ -- 模板的声明和定义
开发语言·c++
澈2072 小时前
深耕进阶 Day1:C 与 C++ 核心差异 + C++ 入门基石
c语言·开发语言·c++
脱氧核糖核酸__3 小时前
LeetCode热题100——234.回文链表(两种解法)
c++·算法·leetcode·链表
愚者游世3 小时前
noexcept 说明符与 noexcept运算符各版本异同
开发语言·c++·程序人生·面试·visual studio
极客BIM工作室4 小时前
OCCT开发实践:空间封闭曲线生成曲面的思考与总结
c++
澈2074 小时前
C++多态编程:从原理到实战
开发语言·c++
6Hzlia4 小时前
【Hot 100 刷题计划】 LeetCode 24. 两两交换链表中的节点 | C++ 精准指针舞步
c++·leetcode·链表
汉克老师5 小时前
GESP2025年6月认证C++五级( 第一部分选择题(9-15))
c++·贪心算法·分治算法·二分算法·gesp5级·gesp五级·高精度除法