C++ 设计模式之工厂模式(Factory)和面试问题

工厂模式(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:为什么不把工厂写成单例?

答题要点:

  • 不是必须
  • 单例会引入全局状态
  • 可测试性下降

成熟回答:

注册式工厂常以单例存在,但这是权衡结果,不是必然。


七、终极总结

工厂模式的价值不在于"创建对象",
而在于"隔离变化、控制依赖方向"。


相关推荐
txinyu的博客2 小时前
C++ 单例模式
c++·单例模式
亓才孓2 小时前
Java第三代时间API
java·开发语言
码农水水2 小时前
京东Java面试被问:Spring Boot嵌入式容器的启动和端口绑定原理
java·开发语言·人工智能·spring boot·面试·职场和发展·php
Yuer20252 小时前
状态不是变量:Rust 量化算子中的 State 工程语义
开发语言·后端·深度学习·机器学习·rust
玖釉-2 小时前
[Vulkan 学习之路] 05 - 缔结契约:创建逻辑设备 (Logical Device)
c++·windows·图形渲染
彩妙不是菜喵2 小时前
c++:初阶/初始模版
开发语言·c++
Allen_LVyingbo2 小时前
具备安全护栏与版本化证据溯源的python可审计急诊分诊平台复现
开发语言·python·安全·搜索引擎·知识图谱·健康医疗
专注于大数据技术栈2 小时前
java学习--LinkedList
java·开发语言·学习
二十六画生的博客2 小时前
20260114外协面试
面试·职场和发展