C++工厂方法模式:原理、实现与最佳实践指南
·
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 日志系统(多输出类型)
- 业务需求:支持文件、控制台、网络日志,根据环境动态切换。
- 技术挑战:硬编码日志类型导致扩展困难,创建逻辑与业务逻辑耦合。
- 模式应用:通过
FileLoggerCreator、ConsoleLoggerCreator隔离不同日志的创建细节,客户端通过工厂接口调用,新增类型只需添加工厂子类。
3.2 游戏角色生成(动态难度)
- 业务需求:根据玩家等级生成不同敌人(低级/高级/精英),属性随等级变化。
- 技术挑战:直接
new LowLevelEnemy()导致角色创建与关卡逻辑紧耦合,新增敌人需修改关卡代码。 - 模式应用:定义
EnemyCreator抽象工厂,子类LowLevelEnemyCreator、HighLevelEnemyCreator分别创建对应敌人,关卡代码通过工厂接口获取敌人实例,支持动态扩展。
3.3 数据库连接(多数据源适配)
- 业务需求:支持 MySQL、PostgreSQL 等多种数据库,根据配置文件切换。
- 技术挑战:不同数据库连接逻辑差异大,硬编码导致切换成本高。
- 模式应用:
DatabaseFactory抽象工厂定义createConnection(),子类MySQLFactory、PgSQLFactory实现具体连接逻辑,配置文件指定数据库类型,工厂动态创建对应连接。
四、避坑指南: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; };
更多推荐


所有评论(0)