new不能直接写死在业务逻辑里,否则更换具体类型需修改所有调用点,增加编译、测试、上线成本;工厂模式通过分离“造什么”与“怎么用”,使业务代码仅依赖接口,不接触new。

为什么 new 不能直接写死在业务逻辑里
因为一旦要换具体类型(比如从 JsonParser 切到 XmlParser),就得改所有调用点,编译、测试、上线成本全涨。工厂模式本质是把“造什么”和“怎么用”拆开——业务代码只认接口,不碰 new。
常见错误现象:std::unique_ptr<base> p = std::make_unique<derived>();</derived> 看似解耦,但业务层仍得包含 Derived.h,头文件依赖没断干净。
- 真正解耦的关键:工厂类单独编译,业务层只通过头文件看到纯虚接口和工厂函数声明
- 工厂函数返回
std::unique_ptr<base>,避免裸指针和内存泄漏 - 不要在工厂内部用
switch或if-else直接 new,优先用映射表(std::map<:string std::function>()>></:string>)支持运行时注册
SimpleFactory::create() 的参数设计陷阱
传字符串字面量最常见,但拼错就崩溃(比如 "json" 写成 "jason"),且无编译期检查。更稳的做法是用枚举:
enum class ParserType { JSON, XML, YAML };
这样编译器能帮你拦住非法值,也方便后续扩展(比如加 ParserType::BINARY)。
立即学习“C++免费学习笔记(深入)”;
- 如果必须用字符串,
create()内部应有兜底逻辑:return nullptr或抛std::invalid_argument,不能静默返回空指针 - 避免把配置项(如文件路径、编码格式)塞进工厂函数参数——那是具体实现类该处理的,工厂只管“选型”
- 返回
std::optional<:unique_ptr>></:unique_ptr>比裸指针更明确表达“可能造不出来”
静态工厂 vs. 实例化工厂:什么时候该用 static
简单工厂通常写成静态函数,因为不需要状态。但一旦涉及资源池、缓存、线程安全或依赖注入(比如工厂本身需要 Logger),就必须实例化。
容易踩的坑:static 成员变量在多线程下未加锁就初始化,导致重复构造或竞态;或者误以为静态工厂能自动管理生命周期,结果单例对象析构顺序引发 crash。
- 纯类型创建(无外部依赖、无状态)→ 用
static成员函数,轻量清晰 - 需要读配置、连数据库、记日志 → 工厂必须是类实例,且生命周期由上层(如
Application类)统一管理 - 别为了“看起来像设计模式”而强行把工厂做成单例——C++ 里全局单例比 Java 更危险,尤其涉及静态对象析构顺序时
模板化简单工厂的适用边界
有人会写 template<typename t> std::unique_ptr<t> create() { return std::make_unique<t>(); }</t></t></typename>,看似通用,实则破坏了“面向接口编程”的初衷——调用方又得知道具体类型名,跟直接 new 差不多。
真正该模板化的是工厂注册机制,比如:
template<typename T> void register_parser(const std::string& name) { registry[name] = []{ return std::make_unique<T>(); };
这样新增类型只需一行注册,不改工厂核心逻辑。
- 模板工厂适合构建工具链(如序列化器、编码器),不适合业务实体(如
User、Order)——后者类型关系复杂,硬套模板反而难维护 - 注意编译单元隔离:模板定义必须在头文件里,否则链接时报
undefined reference - 如果不同模块注册同名类型(比如两个库都注册
"json"),得靠命名空间或前缀区分,否则后注册的覆盖前一个
最常被忽略的一点:工厂返回的对象,其析构时机必须明确。如果工厂内部用了 std::shared_ptr 缓存实例,而业务层又用 std::unique_ptr 接收,就会 double-free。类型擦除和所有权转移,比看上去更脆。











