工厂模式(Factory)
一、问题背景:为什么需要工厂模式
1. 直接 new 带来的问题
典型代码:
cpp
Shape* s = new Circle(10);
问题不在于 new,而在于:
- 类型强耦合
- 违反开闭原则(OCP)
- 创建逻辑分散
- 难以替换实现 / 测试 / 配置化
一旦对象创建逻辑变复杂(参数、配置、平台差异),调用者将被迫了解过多细节。
2. 本质问题抽象
"谁负责决定创建哪一个具体类型?"
- 调用者?
- 对象自己?
- 专门的创建者(Factory)?
二、工厂模式的核心思想
1. 定义
将对象的创建与使用解耦,由专门的工厂负责实例化具体对象。
从依赖关系角度看:
text
调用方 → 抽象接口
工厂 → 具体实现
2. 设计目标
- 消除对具体类型的直接依赖
- 集中管理对象创建逻辑
- 支持运行期多态创建
- 为扩展而非修改而设计
三、工厂模式的三种典型形式
| 模式 | 核心关注点 | 复杂度 |
|---|---|---|
| 简单工厂 | 集中创建逻辑 | ⭐ |
| 工厂方法 | 延迟具体类型决定 | ⭐⭐ |
| 抽象工厂 | 产品族一致性 | ⭐⭐⭐ |
四、简单工厂(Simple Factory)
严格来说不是 GoF 设计模式,但在工程中极其常见。
1. 结构
text
Factory → switch / if → ConcreteProduct
2. 示例
抽象产品
cpp
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
};
具体产品
cpp
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Draw Circle\n";
}
};
class Square : public Shape {
public:
void draw() const override {
std::cout << "Draw Square\n";
}
};
工厂
cpp
enum class ShapeType {
Circle,
Square
};
class ShapeFactory {
public:
static std::unique_ptr<Shape> create(ShapeType type) {
switch (type) {
case ShapeType::Circle:
return std::make_unique<Circle>();
case ShapeType::Square:
return std::make_unique<Square>();
default:
return nullptr;
}
}
};
3. 特点分析
优点
- 使用简单
- 创建逻辑集中
缺点
- 新增类型必须修改工厂
switch不可避免 → 违反 OCP
五、工厂方法(Factory Method)
将"创建哪种产品"的决定权下放给子类
1. 结构
text
Creator(抽象工厂)
└── ConcreteCreator(具体工厂)
└── ConcreteProduct
2. 示例
抽象产品
cpp
class Logger {
public:
virtual ~Logger() = default;
virtual void log(const std::string& msg) = 0;
};
具体产品
cpp
class FileLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "File: " << msg << "\n";
}
};
class ConsoleLogger : public Logger {
public:
void log(const std::string& msg) override {
std::cout << "Console: " << msg << "\n";
}
};
抽象工厂
cpp
class LoggerFactory {
public:
virtual ~LoggerFactory() = default;
virtual std::unique_ptr<Logger> create() const = 0;
};
具体工厂
cpp
class FileLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> create() const override {
return std::make_unique<FileLogger>();
}
};
class ConsoleLoggerFactory : public LoggerFactory {
public:
std::unique_ptr<Logger> create() const override {
return std::make_unique<ConsoleLogger>();
}
};
3. 特点分析
优点
- 遵循开闭原则
- 易于扩展新类型
- 消除了
switch
代价
- 类数量显著增加
- 结构复杂度提高
六、抽象工厂(Abstract Factory)
创建"相关或相互依赖的一组对象"
1. 典型应用场景
- GUI 跨平台(Windows / Linux)
- 渲染后端(OpenGL / Vulkan)
- SLAM 中不同传感器产品族
2. 示例结构
text
AbstractFactory
├── createA()
└── createB()
ConcreteFactory1 → ProductA1 + ProductB1
ConcreteFactory2 → ProductA2 + ProductB2
3. 示例代码
抽象产品
cpp
class Button {
public:
virtual void paint() = 0;
virtual ~Button() = default;
};
class Checkbox {
public:
virtual void paint() = 0;
virtual ~Checkbox() = default;
};
抽象工厂
cpp
class GUIFactory {
public:
virtual ~GUIFactory() = default;
virtual std::unique_ptr<Button> createButton() = 0;
virtual std::unique_ptr<Checkbox> createCheckbox() = 0;
};
具体工厂
cpp
class WindowsFactory : public GUIFactory {
public:
std::unique_ptr<Button> createButton() override;
std::unique_ptr<Checkbox> createCheckbox() override;
};
4. 特点分析
优势
- 保证产品族一致性
- 完全隔离具体实现
不足
- 新增"产品种类"困难
- 接口膨胀风险
七、C++ 工程化要点
1. 返回裸指针 vs 智能指针
| 方式 | 适用场景 |
|---|---|
unique_ptr |
所有权明确(推荐) |
shared_ptr |
共享生命周期 |
| 裸指针 | 极少(非拥有语义) |
2. 注册式工厂(消除 switch)
cpp
using Creator = std::function<std::unique_ptr<Shape>()>;
class Factory {
public:
static Factory& instance() {
static Factory f;
return f;
}
void registerType(std::string name, Creator c) {
creators_[name] = std::move(c);
}
std::unique_ptr<Shape> create(const std::string& name) {
return creators_.at(name)();
}
private:
std::unordered_map<std::string, Creator> creators_;
};
优点
- 插件化
- 配置驱动
- 完全符合 OCP
3. 与模板的关系
- 运行期多态 → 工厂
- 编译期多态 → 模板
混合方案(CRTP + 工厂)在高性能系统中很常见。
八、常见误用与反模式
- 为简单对象滥用工厂
- 工厂类变成"上帝类"
- 把业务逻辑塞进工厂
- 用工厂代替依赖注入
九、什么时候用工厂模式
核心判断标准不是"能不能用",而是**"是否值得引入间接层"**
1. 适合使用的典型信号
(1)对象创建逻辑具有变化性
-
新类型可能频繁加入
-
创建规则可能因配置 / 平台 / 环境变化
-
示例:
- 不同传感器模型
- 不同后端(CPU / GPU)
- 不同日志输出方式
变化点集中在"创建阶段"
(2)调用方不应知道具体类型
如果调用方只关心接口语义:
cpp
process(Detector& detector);
而不应关心:
cpp
ORBDetector / SuperPointDetector / CustomDetector
工厂是依赖反转的自然载体
(3)对象创建代价高或步骤复杂
- 需要读取配置
- 需要资源初始化
- 需要多阶段构造
此时:
new放在业务代码中是结构性污染- 工厂可作为生命周期边界
2. 不适合使用的情况
| 场景 | 原因 |
|---|---|
| 类型固定且简单 | 间接层无收益 |
| 只创建一次的小对象 | 工厂成本 > 收益 |
| 纯值类型(POD) | 不需要多态 |
| 强性能敏感内循环 | 虚函数 + 间接跳转 |
十、在什么模块中最常见(Where It Appears)
1. 系统架构层面(高频)
(1)模块边界 / 子系统入口
- 解码器工厂
- 渲染后端工厂
- 数据源工厂
text
App
└── Factory
└── Subsystem Impl
工厂通常是模块的"门面入口"
2. 中间件与基础设施层(极常见)
| 模块 | 工厂作用 |
|---|---|
| 日志系统 | Console / File / Network |
| 存储系统 | Memory / Disk / Cloud |
| 网络 | TCP / UDP / QUIC |
| 序列化 | JSON / Protobuf / FlatBuffers |
3. SLAM / CV / Robotics(工程经验)
你这个背景下,工厂模式非常常见于:
- 特征提取器(ORB / SIFT / SuperPoint)
- 回环检测模块
- 地图后端优化器
- 传感器模型(LiDAR / Camera / IMU)
典型原因:
- 算法频繁替换
- 实验配置驱动
- 运行期选择
十一、典型 C++ 写法(Recommended Patterns)
1. 最推荐:注册式工厂 + 智能指针
这是现代 C++ 工程中最常用、扩展性最好的一种。
cpp
class Base {
public:
virtual ~Base() = default;
};
using Creator = std::function<std::unique_ptr<Base>()>;
class Factory {
public:
static Factory& instance() {
static Factory f;
return f;
}
void registerCreator(const std::string& key, Creator c) {
creators_[key] = std::move(c);
}
std::unique_ptr<Base> create(const std::string& key) const {
return creators_.at(key)();
}
private:
std::unordered_map<std::string, Creator> creators_;
};
特点
- 无
switch - 完全符合 OCP
- 支持插件化 / 动态库
2. 静态注册(工程常用技巧)
cpp
struct Registrar {
Registrar() {
Factory::instance().registerCreator(
"A", [] { return std::make_unique<A>(); }
);
}
};
static Registrar reg;
常见于
- 算法注册
- 模块自动发现
- Plugin system
3. 模板 + 工厂混合(高性能场景)
cpp
template<typename T>
std::unique_ptr<Base> create() {
return std::make_unique<T>();
}
适合:
- 类型在编译期已知
- 不需要运行期字符串查找
- 追求零开销抽象
4. 工厂 + 配置文件(生产系统)
cpp
auto type = config["detector"];
auto detector = Factory::instance().create(type);
工厂是"配置驱动架构"的关键支点
十二、常见误区(Very Important)
1. 为了"设计模式"而用工厂
错误动机:
"这里好像可以用工厂模式"
正确动机:
"这里存在变化扩散风险"
2. 工厂类膨胀成上帝对象
反例:
cpp
class Factory {
public:
createA();
createB();
createC();
loadConfig();
validate();
};
问题:
- 职责混乱
- 难以维护
- 难以测试
工厂只负责 创建,不负责业务
3. 用工厂代替依赖注入(DI)
工厂:
- 负责创建
DI:
- 负责装配
二者职责不同,但可以配合。
4. 忽视对象生命周期语义
错误:
cpp
Base* create(); // 所有权不清晰
推荐:
cpp
std::unique_ptr<Base> create();
工厂必须明确谁拥有对象
5. 过早抽象
- 当前只有一个实现
- 短期内无变化需求
YAGNI 原则优先
十三、总结
工厂模式不是为了"少写 new",
而是为了 控制变化的传播路径。
选择建议:
- 类型固定、简单 → 不用工厂
- 类型可扩展 → 工厂方法
- 产品族强一致 → 抽象工厂
- 配置 / 插件驱动 → 注册式工厂
下面给你一份面向中高级 C++ / 系统 / SLAM 岗位的「工厂模式面试问题清单」 。
不是背定义,而是考理解、考取舍、考工程经验 。
我按 "从浅到深 + 高频考点" 组织,并给出标准回答要点(不是死答案)。
C++ 工厂模式 面试问题
一、基础理解类
Q1:什么是工厂模式?它解决了什么问题?
答题要点:
- 将对象创建与使用解耦
- 消除对具体类型的直接依赖
- 控制"变化的传播"
- 本质是 依赖反转(DIP)+ 开闭原则(OCP)
工厂模式用于在不修改调用方的情况下,引入或替换具体实现。
Q2:C++ 中常见的工厂模式有哪些?
答题要点:
- 简单工厂(工程常用,非 GoF)
- 工厂方法(Factory Method)
- 抽象工厂(Abstract Factory)
可加一句:
实际工程中,注册式工厂比教科书模式更常见。
Q3:工厂模式和直接 new 的本质区别?
答题要点:
new暴露具体类型- 工厂隐藏类型决策
- 工厂引入了间接层,但换来可扩展性
反问式总结:
如果类型永远不变,就不需要工厂。
二、设计判断类
Q4:什么时候不应该使用工厂模式?
答题要点:
- 类型固定、不会扩展
- 对象是值语义(POD)
- 性能极度敏感的内循环
- 只有一个实现且短期无变化
关键词:YAGNI、过度设计
Q5:简单工厂为什么不符合开闭原则?
答题要点:
- 新增类型必须修改
switch / if - 修改集中在工厂内部
- 违反"对扩展开放,对修改关闭"
可补充:
工程上可通过注册机制弥补这一问题。
Q6:工厂方法 vs 抽象工厂的核心区别?
答题要点:
| 对比点 | 工厂方法 | 抽象工厂 |
|---|---|---|
| 关注点 | 单一产品 | 产品族 |
| 扩展维度 | 新类型 | 新平台 |
| 接口规模 | 小 | 大 |
总结:
工厂方法解决"用哪一个",抽象工厂解决"用哪一套"。
三、C++ 特有问题
Q7:工厂函数应该返回裸指针还是智能指针?
标准答案:
- 优先返回
std::unique_ptr - 明确所有权语义
- 避免内存泄漏
补充说明:
返回裸指针通常意味着"不拥有",但工厂语义下很少成立。
Q8:为什么现代 C++ 工厂常用 std::function + lambda?
答题要点:
- 消除样板代码
- 统一创建接口
- 支持捕获配置 / 状态
- 可与注册机制自然结合
加分点:
性能敏感场景可用函数指针或模板替代。
Q9:工厂模式和模板有什么关系?
答题要点:
- 工厂 → 运行期多态
- 模板 → 编译期多态
- 工厂适合配置驱动
- 模板适合性能敏感、类型固定场景
一句话:
是否需要运行期选择类型,是分界线。
四、工程实战类
Q10:如何在不修改工厂代码的情况下新增类型?
答题要点:
- 注册式工厂
- 静态自动注册
- 插件加载时注册
示例思路:
cpp
Factory::instance().registerCreator("ORB", [] {
return std::make_unique<ORB>();
});
Q11:静态注册工厂有哪些坑?
答题要点:
- 静态初始化顺序问题
- 链接器裁剪(未引用对象被移除)
- 动态库加载顺序
对策关键词:
- 函数内
static - 显式引用
- 插件初始化接口
Q12:工厂模式和依赖注入(DI)的关系?
答题要点:
- 工厂负责"创建"
- DI 负责"装配"
- 工厂是 DI 的一种实现手段
一句话:
工厂是机制,DI 是架构思想。
五、进阶/架构类
Q13:工厂模式的性能代价在哪里?
答题要点:
- 虚函数调用
std::function间接调用- map 查找
优化手段:
- 编译期工厂
- 函数指针
- 枚举 + 表
Q14:什么时候工厂会演变成反模式?
答题要点:
- 工厂类承担业务逻辑
- 工厂知道过多细节
- 工厂数量爆炸
- 所有东西都"Factory 化"
关键词:上帝对象
Q15:在 SLAM / 引擎系统中,哪些模块最适合用工厂?
答题要点:
- 特征提取器
- 匹配器
- 优化后端
- 传感器模型
原因:
- 算法频繁切换
- 实验驱动
- 配置驱动
六、经典"陷阱题"
Q16:shared_ptr 适合做工厂返回值吗?
答题要点:
- 可以,但应谨慎
- 适合共享生命周期
- 否则会掩盖所有权设计问题
面试官想听:
默认
unique_ptr,必要时才shared_ptr。
Q17:为什么不把工厂写成单例?
答题要点:
- 不是必须
- 单例会引入全局状态
- 可测试性下降
成熟回答:
注册式工厂常以单例存在,但这是权衡结果,不是必然。
七、终极总结
工厂模式的价值不在于"创建对象",
而在于"隔离变化、控制依赖方向"。