C++工厂方法模式:原理、实现与最佳实践指南(公众号精简版)

引言:为什么需要工厂方法模式?

在 C++ 开发中,直接用 new 关键字创建对象看似简单,却隐藏着严重的耦合问题。例如:

  • 日志系统需要支持文件、控制台、网络等多种输出方式,硬编码 new FileLogger() 会导致新增输出类型时必须修改所有客户端代码;
  • 游戏角色生成需根据玩家等级动态创建敌人(低级/高级/精英),直接实例化会让角色创建逻辑与战斗逻辑纠缠在一起。

工厂方法模式的核心解决方案:定义一个创建对象的抽象接口,让子类决定实例化哪个类,从而将对象创建与使用彻底解耦。这一机制不仅支持灵活扩展,还能让客户端代码专注于业务逻辑而非对象构造细节。

一、核心概念:4 个组件+1 个原则

1.1 模式定义(GoF经典定义)

定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法使一个类的实例化延迟到其子类。

1.2 核心组件(以日志系统为例)

  • 抽象产品(Product):定义产品接口,如 ILogger 声明 log() 纯虚方法。
  • 具体产品(Concrete Product):实现抽象接口,如 FileLogger(写入文件)、NetworkLogger(网络发送)。
  • 抽象工厂(Creator):声明工厂方法,如 LoggerCreator 声明纯虚方法 createLogger()
  • 具体工厂(Concrete Creator):重写工厂方法生成具体产品,如 FileLoggerCreator 创建 FileLogger

1.3 核心原则:依赖倒置

客户端仅依赖抽象工厂抽象产品,不直接引用具体类。例如:

// 客户端代码仅依赖抽象接口
void useLogger(LoggerCreator& factory) {
    auto logger = factory.createLogger(); // 多态创建产品
    logger->log("业务日志"); 
}

二、实战代码:从基础到高级(C++17/20)

2.1 基础实现:日志系统(折叠代码块)

#include <iostream>
#include <memory> // 智能指针

// 1. 抽象产品
class ILogger {
public:
    virtual ~ILogger() = default; 
    virtual void log(const std::string& message) = 0; // 纯虚方法
};

// 2. 具体产品
class FileLogger : public ILogger {
public:
    void log(const std::string& message) override {
        std::cout << "文件日志: " << message << std::endl; 
    }
};

class NetworkLogger : public ILogger {
public:
    void log(const std::string& message) override {
        std::cout << "网络日志: " << message << std::endl; 
    }
};

// 3. 抽象工厂
class LoggerCreator {
public:
    virtual ~LoggerCreator() = default;
    virtual std::unique_ptr<ILogger> createLogger() = 0; // 工厂方法
    void useLogger(const std::string& message) { // 业务逻辑
        auto logger = createLogger(); 
        logger->log(message); 
    }
};

// 4. 具体工厂
class FileLoggerCreator : public LoggerCreator {
public:
    std::unique_ptr<ILogger> createLogger() override {
        return std::make_unique<FileLogger>(); // 智能指针管理资源
    }
};

class NetworkLoggerCreator : public LoggerCreator {
public:
    std::unique_ptr<ILogger> createLogger() override {
        return std::make_unique<NetworkLogger>(); 
    }
};

// 客户端代码
int main() {
    FileLoggerCreator fileFactory;
    fileFactory.useLogger("用户登录成功"); // 输出:文件日志: 用户登录成功

    NetworkLoggerCreator netFactory;
    netFactory.useLogger("API请求超时"); // 输出:网络日志: API请求超时
    return 0;
}
关键特性:
  • 智能指针std::unique_ptr 自动管理内存,避免泄漏。
  • 多态创建createLogger() 由子类重写,决定实例化哪个产品。

2.2 增强实现:支持运行时配置(注册表模式)

当需要根据配置文件动态选择产品类型时,可通过全局注册表实现:

#include <map>
#include <functional> // std::function

// 日志类型枚举
enum class LoggerType { File, Network, Console };

class LoggerFactory {
public:
    // 定义创建函数类型:无参数,返回ILogger智能指针
    using CreatorFunc = std::function<std::unique_ptr<ILogger>()>;

    // 注册创建函数(运行时动态添加)
    static void registerCreator(LoggerType type, CreatorFunc creator) {
        getRegistry()[type] = std::move(creator);
    }

    // 根据类型创建产品
    static std::unique_ptr<ILogger> create(LoggerType type) {
        auto it = getRegistry().find(type);
        if (it != getRegistry().end()) {
            return it->second(); // 调用注册的创建函数
        }
        throw std::invalid_argument("未知日志类型");
    }

private:
    // 静态注册表(线程安全初始化)
    static std::map<LoggerType, CreatorFunc>& getRegistry() {
        static std::map<LoggerType, CreatorFunc> registry; 
        return registry;
    }
};

// 注册示例(程序启动时执行)
void initLoggers() {
    LoggerFactory::registerCreator(LoggerType::File, []() {
        return std::make_unique<FileLogger>(); 
    });
    LoggerFactory::registerCreator(LoggerType::Network, []() {
        return std::make_unique<NetworkLogger>(); 
    });
}
核心优势:
  • 运行时扩展:新增日志类型(如 ConsoleLogger)只需注册,无需修改工厂代码。
  • 配置驱动:结合配置文件(如 JSON 中指定 logger_type: "File"),动态选择产品。

三、应用场景:3 个典型案例

3.1 日志系统(多输出类型)

  • 业务需求:支持文件、控制台、网络日志,根据环境动态切换。
  • 技术挑战:硬编码日志类型导致扩展困难,创建逻辑与业务逻辑耦合。
  • 模式应用:通过 FileLoggerCreatorConsoleLoggerCreator 隔离不同日志的创建细节,客户端通过工厂接口调用,新增类型只需添加工厂子类。

3.2 游戏角色生成(动态难度)

  • 业务需求:根据玩家等级生成不同敌人(低级/高级/精英),属性随等级变化。
  • 技术挑战:直接 new LowLevelEnemy() 导致角色创建与关卡逻辑紧耦合,新增敌人需修改关卡代码。
  • 模式应用:定义 EnemyCreator 抽象工厂,子类 LowLevelEnemyCreatorHighLevelEnemyCreator 分别创建对应敌人,关卡代码通过工厂接口获取敌人实例,支持动态扩展。

3.3 数据库连接(多数据源适配)

  • 业务需求:支持 MySQL、PostgreSQL 等多种数据库,根据配置文件切换。
  • 技术挑战:不同数据库连接逻辑差异大,硬编码导致切换成本高。
  • 模式应用DatabaseFactory 抽象工厂定义 createConnection(),子类 MySQLFactoryPgSQLFactory 实现具体连接逻辑,配置文件指定数据库类型,工厂动态创建对应连接。

四、避坑指南:3 个常见问题+解决方案

4.1 问题1:类数量膨胀(产品与工厂1:1)

  • 现象:每新增一个产品需对应一个工厂类,导致类数量爆炸。
  • 解决方案模板工厂(泛型抽象)
    template<typename Product>
    class GenericFactory : public LoggerCreator {
    public:
        std::unique_ptr<ILogger> createLogger() override {
            return std::make_unique<Product>(); // 通用创建逻辑
        }
    };
    // 使用:GenericFactory<FileLogger> fileFactory;
    

4.2 问题2:客户端需显式选择工厂

  • 现象:客户端必须知道 FileLoggerCreator 等具体工厂,违背依赖倒置。
  • 解决方案配置驱动+工厂注册表
    结合上文注册表模式,通过配置文件指定工厂类型(如 logger_factory: "File"),由全局工厂管理器根据类型动态获取工厂实例。

4.3 问题3:资源管理风险(原始指针泄漏)

  • 现象:工厂返回原始指针,客户端忘记 delete 导致内存泄漏。
  • 解决方案强制使用智能指针
    工厂方法返回 std::unique_ptr<Product>,利用 RAII 自动释放资源,避免手动管理。

五、现代C++最佳实践

5.1 智能指针必用(std::unique_ptr/std::shared_ptr

  • 原则:工厂方法必须返回智能指针,禁止返回原始指针。
  • 示例std::unique_ptr<ILogger> createLogger() override,使用 std::make_unique 创建对象,确保异常安全。

5.2 C++20 概念约束(强化接口检查)

  • 作用:编译期验证工厂是否符合接口规范,避免运行时错误。
    #include <concepts>
    template<typename Factory>
    concept ValidLoggerFactory = requires(Factory f) {
        { f.createLogger() } -> std::derived_from<ILogger>; // 返回值必须是ILogger子类
    };
    

5.3 注册表模式+自动注册(简化扩展)

  • 实现:通过静态成员初始化自动注册工厂,新增产品时无需手动调用 registerCreator
    // 自动注册器模板
    template<typename LoggerImpl, LoggerType Type>
    struct AutoRegistrar {
        AutoRegistrar() {
            LoggerFactory::registerCreator(Type, []() {
                return std::make_unique<LoggerImpl>();
            });
        }
    };
    // 使用:在ConsoleLogger类中声明静态注册器
    class ConsoleLogger : public ILogger {
    private:
        static AutoRegistrar<ConsoleLogger, LoggerType::Console> registrar;
    };
    

更多推荐