工厂模式核心是将new操作从调用方剥离,通过统一接口控制对象创建,实现创建逻辑集中化与编译依赖收敛;需返回智能指针的基类接口,调用方仅包含工厂和基类头文件。

工厂模式在 C++ 里不是靠“定义一个叫 Factory 的类”就完事的,核心是把 new 操作从调用方剥离,交由统一接口控制对象创建逻辑。直接 new 具体类,等于把耦合写死;用工厂,才能让新增子类不改已有代码。
为什么不能直接 new,而要用工厂函数或工厂类
硬编码 new Derived() 会让业务逻辑和具体类型强绑定。比如日志模块要支持 FileLogger、ConsoleLogger、NetLogger,如果每个地方都 new FileLogger,后续加个 DBLogger 就得翻遍所有 new 的位置改代码。
工厂解决的是「创建逻辑集中化」问题,不是语法炫技。常见错误是:只封装了 new,但没隔离类型依赖——比如工厂返回 Derived* 却要求调用方包含 Derived.h,那头文件依赖照样散开。
- 真正解耦的关键:工厂返回基类指针(如
std::unique_ptr),且调用方只 include 工厂头文件和基类声明 - 工厂内部才 include 具体子类头文件,实现编译依赖收敛
- 避免裸指针:优先用
std::unique_ptr或std::shared_ptr,防止资源泄漏
简单工厂:用 switch 或 map 实现创建逻辑
简单工厂不是 GoF 23 种之一,但最常用、最易理解。它本质是一个创建函数或静态方法,输入类型标识,输出对象指针。
立即学习“C++免费学习笔记(深入)”;
示例中用字符串标识比枚举更灵活(配置文件可读),但要注意大小写和拼写一致性:
std::unique_ptrcreateLogger(const std::string& type) { if (type == "file") return std::make_unique (); if (type == "console") return std::make_unique (); if (type == "net") return std::make_unique (); throw std::runtime_error("Unknown logger type: " + type); }
容易踩的坑:
- 忘记处理未知类型 → 程序崩溃或未定义行为,必须有兜底(抛异常或返回 nullptr)
- 工厂函数被频繁调用却没缓存,每次都要字符串比较 → 高频场景建议预建
std::unordered_map<:string std::function>()>> - 返回裸指针(
new Logger)→ 调用方极易忘 delete,必须用智能指针
工厂类如何支持运行时注册新类型(避免修改工厂源码)
当第三方模块要贡献自己的 MyCustomLogger,你不希望每次都要打开工厂源码加一行 if (type=="custom")...。这时需要「注册机制」。
典型做法是定义一个全局注册表,配合静态对象触发注册:
class LoggerFactory {
public:
using Creator = std::function()>;
static void registerCreator(const std::string& type, Creator c) {
creators()[type] = std::move(c);
}
static std::unique_ptr create(const std::string& type) {
auto it = creators().find(type);
if (it != creators().end()) return it->second();
throw std::runtime_error("No creator registered for: " + type);
}
private:
static std::unordered_map& creators() {
static std::unordered_map m;
return m;
}
};
使用时,在自定义类的 .cpp 文件末尾加一句:
static bool reg = []{
LoggerFactory::registerCreator("custom", []{ return std::make_unique(); });
return true;
}();
注意点:
- 注册必须发生在
main()之前,靠静态局部变量或静态变量初始化保证顺序 - map 查找是 O(1),但首次调用
creators()会构造 map,线程不安全;多线程环境需加std::call_once或初始化时预热 - 注册名重复不会报错,后注册的会覆盖前一个 —— 这是设计选择,不是 bug
工厂模式真正的复杂点不在结构,而在生命周期管理与依赖可见性。很多人写了工厂类,却让所有子类头文件暴露给主程序,结果改一个子类就得全量重编。解耦是否成功,看编译依赖图是否收敛到工厂和基类两个头文件。











