🎯 导读:本文讲解原型模式——不用
new从头构建对象,而是像复印机一样直接”克隆”已有对象!适合需要频繁创建相似对象的场景。
🖨️ 原型模式(Prototype Pattern) 是创建型设计模式中最特别、也最符合人类日常直觉的一个。
如果说”工厂模式”是按图纸在流水线上从零开始制造产品 🏭,那么”原型模式”就是 复印机 📠 或 细胞分裂 🧬。它的核心思想极其简单:不要用 new 从头构建一个复杂的对象,而是拿一个已经存在的、配置好的对象作为”原型(Prototype)”,直接把它”克隆(Clone)”一份出来。
📚 1. 客户端(使用者)需要知道哪些类?
在这个模式下,客户端程序员的负担被降到了极低:
✅ 你需要知道的类:
| 序号 | 类名 | 说明 |
|---|---|---|
| 1️⃣ | 抽象原型接口Prototype Interface |
接收克隆对象,调用 clone() 方法 |
| 2️⃣ | 原型管理器/注册表Prototype Registry |
存放配置好的原型(如 std::map),按名字获取克隆体 🗂️ |
❌ 你不需要/绝不应该知道的类:
- 具体的实现类(Concrete Prototype): 客户端完全不需要知道正在克隆的是什么子类。只需拿到基类指针,大喊一声:“克隆你自己!” 🎤,多态就会在底层搞定一切
🎮 2. 生动的实战场景:游戏里的”怪物生成器”
假设你在开发一款大型动作角色扮演游戏(ARPG)。游戏里有成百上千种怪物,比如”持盾哥布林”、”火焰飞龙”等。
❌ 痛点(没有原型模式)
每次要在屏幕上刷出一只怪,你都要:
1 | new Goblin(); // 创建对象 |
刷 100 只哥布林,这段初始化代码就要跑 100 遍,极其消耗 CPU 性能和时间 ⏱️💸
✅ 解法(原型模式)
在游戏加载关卡时,我们只精心捏出一只完美的”持盾哥布林”对象,把它作为原型(种子) 🌱 放进注册表里。
当游戏开始,需要刷怪时,我们直接对着这只原型哥布林按下 Ctrl+C 和 Ctrl+V(调用 clone())。
🚀 瞬间,100 只一模一样、属性全满的哥布林就被复制出来了,性能极高!
📊 UML 类图
classDiagram
class IMonster {
<>
+clone()
}
class Goblin {
-hp: int
-armor: int
-weapon: string
+clone()
}
class Dragon {
-hp: int
-fireDamage: int
+clone()
}
class MonsterRegistry {
-prototypes: map
+register(name, monster)
+create(name)
}
IMonster <|-- Goblin : 实现
IMonster <|-- Dragon : 实现
MonsterRegistry ..> IMonster : 克隆
💻 3. C++ 现代代码生动演绎
在 C++ 中,原型模式的本质其实就是 多态封装下的拷贝构造函数 🧬。我们来看代码:
1 |
|
🔰 初学者版本:不用智能指针的原型模式
💡 给初学者的说明:上面的代码使用了
std::unique_ptr(智能指针),这是现代 C++ 的推荐做法。但如果你还不熟悉智能指针,下面这个版本使用原始指针,更容易理解!
1 |
|
🔑 两个版本的关键区别
| 特性 | 智能指针版本 | 原始指针版本 |
|---|---|---|
| 内存管理 | 自动释放 ✅ | 手动 delete ⚠️ |
| 代码复杂度 | 稍复杂 | 更简单 |
| 安全性 | 不会内存泄漏 | 容易忘记释放 💥 |
| 学习曲线 | 需要懂智能指针 | 只需懂 new/delete |
📌 建议:先理解原始指针版本,再学习智能指针版本。实际项目中强烈推荐使用智能指针!
⚠️ 4. C++ 程序员必须面对的”深渊”:深拷贝与浅拷贝
原型模式在 C++ 中有一个极其致命的坑,也就是面试必考题:深拷贝(Deep Copy)vs. 浅拷贝(Shallow Copy) 🕳️
在上面的代码中,return std::make_unique<Goblin>(*this); 使用了编译器自动生成的默认拷贝构造函数。
✅ 安全的情况
如果你的类里只有普通变量(如 int, double)和标准容器(如 std::string, std::vector),默认拷贝是安全的(深拷贝) 🛡️
🔥 致命危险
如果你的类里面有一个裸指针(比如 int* data),默认拷贝只会复制这个指针的地址(浅拷贝)。
1 | 原型对象 ──► [内存块 A] ◄── 克隆对象 |
结果就是:原型和克隆体共享了同一块内存!一旦原型被销毁,克隆体里的指针就变成了悬空指针(Dangling Pointer) 💀,程序直接崩溃!
💡 解决办法
如果你的原型类管理了堆内存,你必须手动重写类的拷贝构造函数,在里面为克隆体重新 new 一块独立的内存并复制数据(实现深拷贝)。
⚖️ 5. 优缺点总结
✅ 优点:
| 优点 | 说明 |
|---|---|
| 🚀 极致的性能 | 绕过了复杂的构造函数调用逻辑,直接在内存级别进行复制 |
| 🔄 动态配置 | 相比于死板的工厂类,原型模式可以在运行时动态地增加或删除原型种类 |
❌ 缺点:
| 缺点 | 说明 |
|---|---|
| 😵 实现复杂 | 为每一个类配备一个完美的 clone() 方法有时极其困难(尤其是当类内部存在复杂的循环引用、或者持有不可拷贝的资源如网络 Socket、文件句柄时) |
🎯 总结
💡 在 C++ 中,理解了拷贝构造函数,就等于理解了原型模式的本质。
你对 C++ 的深拷贝和浅拷贝机制足够熟悉了吗?如果不熟悉,推荐阅读:🌊 浅拷贝 vs 🏗️ 深拷贝 —— 一文看懂!
📚 这篇文章用生动的”复印钥匙”比喻,配合完整代码示例,帮你彻底搞懂深拷贝与浅拷贝的区别!
- 本文作者: 迪丽惹Bug
- 本文链接: https://lyroom.github.io/2026/04/29/原型模式-对象复印机/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!