工厂模式、多态与接口设计

工厂模式、多态与接口设计

时间:2026/04/09

关键词:抽象接口、虚函数、工厂函数、依赖倒置、override、虚析构、unique_ptr
核心目标:理解什么时候该把“创建对象”和“使用对象”分开。


1. 为什么需要工厂模式

很多代码的问题不是“不会 new”,而是:

  • 调用方知道太多具体类型
  • 构造逻辑散落各处
  • 后续替换实现很痛苦

工厂模式的核心价值是:

  • 把对象创建逻辑集中起来
  • 让调用方依赖抽象接口,而不是具体实现

2. 多态接口的基础

1
2
3
4
5
6
7
8
9
10
11
12
struct Pet {
virtual ~Pet() = default;
virtual void speak() = 0;
};

struct Cat : Pet {
void speak() override { std::puts("meow"); }
};

struct Dog : Pet {
void speak() override { std::puts("woof"); }
};

这里要点有两个:

  • 基类析构函数要么虚,要么不允许多态删除
  • 派生类重写时用 override

3. 一个最简单的工厂函数

1
2
3
4
5
6
7
8
#include <memory>
#include <string>

std::unique_ptr<Pet> make_pet(const std::string& kind) {
if (kind == "cat") return std::make_unique<Cat>();
if (kind == "dog") return std::make_unique<Dog>();
return nullptr;
}

调用方只关心:

  • 我要一个 Pet

而不关心:

  • 具体怎么构造 Cat / Dog

4. 为什么返回 unique_ptr

返回裸指针会引入一个问题:

  • 谁负责 delete

std::unique_ptr 更清晰:

  • 工厂负责创建
  • 调用方接管独占所有权

这是现代 C++ 工厂接口最常见的实践。


5. 简单工厂 vs 工厂方法

5.1 简单工厂

一个集中函数,根据参数分支创建对象。

优点:

  • 简单直接

缺点:

  • 新增类型时可能要改原工厂

5.2 工厂方法

把“创建哪种对象”交给子类。

适合:

  • 框架式扩展
  • 产品族较复杂

但对小项目来说,简单工厂已经够用。


6. 接口设计比模式名更重要

真正工程里,更该关注这些问题:

  • 基类是不是表达了稳定抽象
  • 调用方是否真的不需要知道具体类型
  • 返回所有权是否清晰
  • 是否需要注册表或插件化

很多时候模式本身不复杂,难的是接口边界。


7. 一个更贴近工程的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct Reducer {
virtual ~Reducer() = default;
virtual int init() const = 0;
virtual int combine(int a, int b) const = 0;
};

struct SumReducer : Reducer {
int init() const override { return 0; }
int combine(int a, int b) const override { return a + b; }
};

struct MulReducer : Reducer {
int init() const override { return 1; }
int combine(int a, int b) const override { return a * b; }
};

这里“算法骨架”不变,“聚合策略”可替换。
这类设计常和工厂、策略模式一起出现。


8. 工厂模式的常见扩展

8.1 注册表工厂

把字符串或类型 id 映射到创建函数:

  • 更适合插件化
  • 更适合可扩展系统

8.2 抽象工厂

如果你需要创建一整组关联对象,就可能进入抽象工厂场景。

例如:

  • UI 皮肤
  • 跨平台组件族

9. 常见坑

9.1 基类没有虚析构

多态删除会出问题。

9.2 工厂返回裸指针

所有权不清晰。

9.3 为了“用模式而用模式”

小项目里过度抽象只会增加复杂度。

9.4 基类接口设计得太宽

会让派生类被迫实现很多并不需要的东西。


10. 一页总结

工厂模式最核心的收益不是“设计模式名词”,而是:

  1. 创建逻辑集中
  2. 调用方依赖抽象
  3. 所有权表达清晰

如果只记一句:

当“对象怎么创建”开始影响“对象怎么使用”时,就该考虑把创建逻辑抽出来。