【c++面向对象编程】第39篇:简单工厂模式与工厂方法模式:C++实现

目录

一、为什么需要工厂?

[二、简单工厂(Simple Factory)](#二、简单工厂(Simple Factory))

结构

实现

简单工厂的优点与缺点

[三、工厂方法模式(Factory Method)](#三、工厂方法模式(Factory Method))

结构

实现

工厂方法模式与开闭原则

[四、简单工厂 vs 工厂方法](#四、简单工厂 vs 工厂方法)

选择指南

五、完整例子:游戏角色创建

六、常见误区

[误区1:工厂方法就是简单工厂 + switch](#误区1:工厂方法就是简单工厂 + switch)

误区2:每个类都要配一个工厂

误区3:工厂一定是单例

七、这一篇的收获


一、为什么需要工厂?

先看一个没有工厂的例子:

cpp

复制代码
class Logger {
public:
    virtual void log(const string& msg) = 0;
    virtual ~Logger() = default;
};

class FileLogger : public Logger {
public:
    void log(const string& msg) override {
        cout << "写入文件: " << msg << endl;
    }
};

class DatabaseLogger : public Logger {
public:
    void log(const string& msg) override {
        cout << "写入数据库: " << msg << endl;
    }
};

// 客户端代码
int main() {
    string type = "file";  // 可能来自配置文件
    
    Logger* logger;
    if (type == "file") {
        logger = new FileLogger();      // 直接 new
    } else {
        logger = new DatabaseLogger();
    }
    
    logger->log("Hello");
    delete logger;
}

问题:

  • 创建逻辑散落在客户端

  • 新增 CloudLogger 需要修改所有创建点

  • 客户端需要知道所有具体类

工厂模式的目标:把"选择哪个类"和"如何创建"的职责集中到一个地方。


二、简单工厂(Simple Factory)

结构

text

复制代码
┌─────────────┐      ┌─────────────────┐
│   Product   │◄─────│  SimpleFactory  │
│ (抽象接口)  │      │  + create(type) │
└─────────────┘      └─────────────────┘
       ▲
       │
┌──────────┐ ┌───────────┐ ┌───────────┐
│ ProductA │ │ ProductB  │ │ ProductC  │
└──────────┘ └───────────┘ └───────────┘

实现

cpp

复制代码
#include <iostream>
#include <memory>
#include <string>
using namespace std;

// 产品接口
class Logger {
public:
    virtual void log(const string& msg) = 0;
    virtual ~Logger() = default;
};

// 具体产品 A
class FileLogger : public Logger {
    string filename;
public:
    FileLogger(const string& f = "app.log") : filename(f) {}
    void log(const string& msg) override {
        cout << "[File:" << filename << "] " << msg << endl;
    }
};

// 具体产品 B
class DatabaseLogger : public Logger {
    string connection;
public:
    DatabaseLogger(const string& conn = "localhost") : connection(conn) {}
    void log(const string& msg) override {
        cout << "[DB:" << connection << "] " << msg << endl;
    }
};

// 具体产品 C
class CloudLogger : public Logger {
    string endpoint;
public:
    CloudLogger(const string& url = "https://api.log.com") : endpoint(url) {}
    void log(const string& msg) override {
        cout << "[Cloud:" << endpoint << "] " << msg << endl;
    }
};

// ========== 简单工厂 ==========
class LoggerFactory {
public:
    enum Type { FILE, DATABASE, CLOUD };
    
    static unique_ptr<Logger> create(Type type) {
        switch (type) {
            case FILE:
                return make_unique<FileLogger>();
            case DATABASE:
                return make_unique<DatabaseLogger>();
            case CLOUD:
                return make_unique<CloudLogger>();
            default:
                return nullptr;
        }
    }
    
    // 也可以根据字符串创建
    static unique_ptr<Logger> create(const string& type) {
        if (type == "file") return make_unique<FileLogger>();
        if (type == "database") return make_unique<DatabaseLogger>();
        if (type == "cloud") return make_unique<CloudLogger>();
        return nullptr;
    }
};

// 客户端代码
int main() {
    auto logger1 = LoggerFactory::create(LoggerFactory::FILE);
    logger1->log("用户登录");
    
    auto logger2 = LoggerFactory::create("cloud");
    logger2->log("系统启动");
    
    return 0;
}

简单工厂的优点与缺点

优点 缺点
简单直观,容易理解 违背开闭原则:每加新产品都要改工厂类
集中管理创建逻辑 工厂类会越来越臃肿
客户端与具体类解耦 无法通过继承扩展

三、工厂方法模式(Factory Method)

结构

text

复制代码
┌─────────────┐      ┌─────────────┐
│   Product   │      │   Factory   │
│ (抽象接口)  │      │ + create()  │
└─────────────┘      └─────────────┘
       ▲                     ▲
       │                     │
┌──────────┐          ┌─────────────┐
│ ProductA │          │ FactoryA    │
└──────────┘          │ + create()  │
                      └─────────────┘

核心思想:把工厂也抽象化。每个具体产品对应一个具体工厂,新增产品只需新增工厂类,不需要修改已有代码。

实现

cpp

复制代码
#include <iostream>
#include <memory>
#include <string>
using namespace std;

// ========== 产品接口 ==========
class Logger {
public:
    virtual void log(const string& msg) = 0;
    virtual ~Logger() = default;
};

// 具体产品
class FileLogger : public Logger {
    string filename;
public:
    FileLogger(const string& f = "app.log") : filename(f) {}
    void log(const string& msg) override {
        cout << "[File:" << filename << "] " << msg << endl;
    }
};

class DatabaseLogger : public Logger {
    string connection;
public:
    DatabaseLogger(const string& conn = "localhost") : connection(conn) {}
    void log(const string& msg) override {
        cout << "[DB:" << connection << "] " << msg << endl;
    }
};

class CloudLogger : public Logger {
    string endpoint;
public:
    CloudLogger(const string& url = "https://api.log.com") : endpoint(url) {}
    void log(const string& msg) override {
        cout << "[Cloud:" << endpoint << "] " << msg << endl;
    }
};

// ========== 工厂接口 ==========
class LoggerFactory {
public:
    virtual unique_ptr<Logger> createLogger() = 0;
    virtual ~LoggerFactory() = default;
};

// 具体工厂 A
class FileLoggerFactory : public LoggerFactory {
    string filename;
public:
    FileLoggerFactory(const string& f = "app.log") : filename(f) {}
    unique_ptr<Logger> createLogger() override {
        return make_unique<FileLogger>(filename);
    }
};

// 具体工厂 B
class DatabaseLoggerFactory : public LoggerFactory {
    string connection;
public:
    DatabaseLoggerFactory(const string& conn = "localhost") : connection(conn) {}
    unique_ptr<Logger> createLogger() override {
        return make_unique<DatabaseLogger>(connection);
    }
};

// 具体工厂 C
class CloudLoggerFactory : public LoggerFactory {
    string endpoint;
public:
    CloudLoggerFactory(const string& url = "https://api.log.com") : endpoint(url) {}
    unique_ptr<Logger> createLogger() override {
        return make_unique<CloudLogger>(endpoint);
    }
};

// ========== 客户端代码 ==========
class Application {
    unique_ptr<LoggerFactory> factory;
public:
    Application(unique_ptr<LoggerFactory> f) : factory(move(f)) {}
    
    void run() {
        auto logger = factory->createLogger();
        logger->log("应用程序启动");
        // 业务逻辑...
        logger->log("应用程序关闭");
    }
};

int main() {
    // 通过配置文件或命令行决定使用哪个工厂
    string config = "cloud";
    
    unique_ptr<LoggerFactory> factory;
    if (config == "file") {
        factory = make_unique<FileLoggerFactory>("custom.log");
    } else if (config == "database") {
        factory = make_unique<DatabaseLoggerFactory>("prod-db");
    } else {
        factory = make_unique<CloudLoggerFactory>("https://mycloud/log");
    }
    
    Application app(move(factory));
    app.run();
    
    return 0;
}

工厂方法模式与开闭原则

text

复制代码
现有产品:FileLogger, DatabaseLogger
新增产品:CloudLogger

需要修改的代码:
  - 新增 CloudLogger 类
  - 新增 CloudLoggerFactory 类
  
不需要修改的代码:
  - Logger 接口 ✓
  - LoggerFactory 接口 ✓
  - Application 类 ✓
  - FileLogger / DatabaseLogger / 其工厂 ✓

完全符合开闭原则:对扩展开放(加新类),对修改关闭(不改旧类)。


四、简单工厂 vs 工厂方法

维度 简单工厂 工厂方法
工厂数量 1 个 每个产品 1 个
开闭原则 ❌ 违背(改工厂类) ✅ 符合(加新工厂类)
代码量 多(类数量翻倍)
复杂度 中等
适用场景 产品种类稳定 产品种类经常增加
扩展性

选择指南

text

复制代码
产品种类是否稳定?
├── 是 → 简单工厂(足够)
└── 否 → 工厂方法
        ├── 或者抽象工厂(下一篇)

五、完整例子:游戏角色创建

cpp

复制代码
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

// ========== 产品:角色 ==========
class Character {
protected:
    string name;
    int health;
public:
    Character(const string& n, int hp) : name(n), health(hp) {}
    virtual void attack() = 0;
    virtual void display() const {
        cout << name << " (HP:" << health << ")";
    }
    virtual ~Character() = default;
};

class Warrior : public Character {
public:
    Warrior(const string& n) : Character(n, 150) {}
    void attack() override {
        cout << name << " 使用旋风斩!" << endl;
    }
};

class Mage : public Character {
public:
    Mage(const string& n) : Character(n, 80) {}
    void attack() override {
        cout << name << " 施放火球术!" << endl;
    }
};

class Archer : public Character {
public:
    Archer(const string& n) : Character(n, 100) {}
    void attack() override {
        cout << name << " 射出穿云箭!" << endl;
    }
};

// ========== 工厂接口 ==========
class CharacterFactory {
public:
    virtual unique_ptr<Character> create(const string& name) = 0;
    virtual ~CharacterFactory() = default;
};

// 具体工厂
class WarriorFactory : public CharacterFactory {
public:
    unique_ptr<Character> create(const string& name) override {
        return make_unique<Warrior>(name);
    }
};

class MageFactory : public CharacterFactory {
public:
    unique_ptr<Character> create(const string& name) override {
        return make_unique<Mage>(name);
    }
};

class ArcherFactory : public CharacterFactory {
public:
    unique_ptr<Character> create(const string& name) override {
        return make_unique<Archer>(name);
    }
};

// ========== 游戏系统 ==========
class Game {
    vector<unique_ptr<Character>> team;
public:
    void recruit(CharacterFactory& factory, const string& name) {
        team.push_back(factory.create(name));
        cout << "招募: ";
        team.back()->display();
        cout << endl;
    }
    
    void battle() {
        cout << "\n=== 战斗开始 ===" << endl;
        for (auto& c : team) {
            c->attack();
        }
    }
};

int main() {
    Game game;
    
    WarriorFactory warriorFactory;
    MageFactory mageFactory;
    ArcherFactory archerFactory;
    
    // 玩家选择职业
    int choice;
    cout << "选择职业: 1.战士 2.法师 3.弓箭手" << endl;
    cin >> choice;
    
    if (choice == 1) {
        game.recruit(warriorFactory, "亚瑟");
    } else if (choice == 2) {
        game.recruit(mageFactory, "甘道夫");
    } else {
        game.recruit(archerFactory, "罗宾汉");
    }
    
    // 新增角色(直接使用工厂)
    game.recruit(warriorFactory, "贝奥武夫");
    game.recruit(mageFactory, "梅林");
    
    game.battle();
    
    // 扩展新职业 Paladin:
    // 1. 添加 class Paladin : public Character
    // 2. 添加 class PaladinFactory : public CharacterFactory
    // 3. 不需要修改任何已有代码
    
    return 0;
}

六、常见误区

误区1:工厂方法就是简单工厂 + switch

工厂方法的本质是多态 ,不是条件判断。简单工厂才用 switch

误区2:每个类都要配一个工厂

只有创建逻辑复杂(需要参数计算、条件判断、资源管理等)时才需要工厂。简单的 make_unique 不需要封装。

误区3:工厂一定是单例

工厂可以是单例,但不是必须。每个工厂可以有状态(如上面的 FileLoggerFactory 可以持有文件名)。


七、这一篇的收获

你现在应该理解:

  • 简单工厂:一个工厂类根据参数创建不同产品;简单但违背开闭原则

  • 工厂方法:每个产品有自己的工厂类;符合开闭原则,但类数量翻倍

  • 选择标准:产品稳定用简单工厂,产品频繁增加用工厂方法

  • 核心价值 :将 new 的职责集中到工厂,客户端面向接口编程

💡 小作业:用工厂方法模式实现一个 Transport 体系(TruckShipAirplane)。Logistics 类使用 TransportFactory 创建运输工具。添加一个新的 Train 类型,验证不需要修改已有代码。


下一篇预告:第40篇《单例模式(Singleton)的多种C++实现》------全局唯一实例。饿汉式、懒汉式、双检锁、Meyers Singleton......哪种是线程安全的?C++11 之后的最佳实践是什么?下篇详解。

相关推荐
雪靡10 小时前
Visual Studio 2026 优雅的给Cmake设置大代理
c++·ide·cmake·visual studio
wengqidaifeng10 小时前
C++从菜鸟到强手:2.类和对象(上)—— 从结构体到类的跨越
java·开发语言·c++
追烽少年x10 小时前
STL中的设计模式(二)
c++·设计模式
沈阳信息学奥赛培训10 小时前
C++ 位运算练习题
开发语言·c++
小燚~10 小时前
MSVCR100.dII报错问题处理
c++·windows·qt
Oj92q85H510 小时前
如何在Dev-C++中使用TDM-GCC编译多个文件
开发语言·c++
wengqidaifeng10 小时前
C++从菜鸟到强手:2.类和对象(下)—— 进阶特性与完整日期类实现
开发语言·c++
Oj92q85H510 小时前
如何在Dev-C++中设置TDM-GCC编译器
开发语言·c++
学无止境_永不停歇11 小时前
从零手写高性能 C++ TCP 服务器框架(一):项目介绍
linux·服务器·c++·中间件