原型模式
. 原型模式 (Prototype Pattern)
场景设定:游戏中的怪物军团
假设你在开发一个 RPG 游戏,需要生成 1000 个“兽人步兵”。
- 每个兽人初始化时,都需要加载模型文件、读取纹理、计算碰撞体积……这个过程非常耗时(假设需要 100ms)。
- 如果用
new Orc()循环 1000 次,加载时间就是 100秒,游戏直接卡死。 - 解决方案: 先创建一个“兽人原型”,配置好所有属性。然后剩下的 999 个,直接克隆这个原型(内存拷贝),速度极快。
代码示例
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
// ==========================================
// 1. 抽象原型 (Prototype Interface)
// 核心:定义 Clone 接口
// ==========================================
class Monster {
public:
virtual ~Monster() {}
// 【核心方法】 clone
// 返回基类指针,利用多态机制
virtual Monster* clone() = 0;
virtual void Show() = 0;
};
// ==========================================
// 2. 具体原型 (Concrete Prototype)
// 变化点:不同的怪物类型
// ==========================================
class Orc : public Monster {
private:
string name;
int hp;
// 假设这是一个很耗资源的指针(模拟深拷贝需求)
int* weaponAttr;
public:
// 构造函数:模拟耗时的初始化
Orc(string n, int h, int w) : name(n), hp(h) {
// 假设这里进行了昂贵的资源加载...
weaponAttr = new int(w);
cout << " [系统] " << name << " 模型加载完毕 (耗时操作)" << endl;
}
// 【关键点 1】拷贝构造函数 (Copy Constructor)
// C++ 中实现原型的核心!必须处理深拷贝!
Orc(const Orc& other) {
this->name = other.name;
this->hp = other.hp;
// 深拷贝:重新申请内存,复制数值,而不是复制指针地址
if (other.weaponAttr) {
this->weaponAttr = new int(*other.weaponAttr);
} else {
this->weaponAttr = nullptr;
}
cout << " [系统] " << name << " 被克隆了 (快速内存拷贝)" << endl;
}
~Orc() {
if (weaponAttr) delete weaponAttr;
}
// 【关键点 2】实现 clone 方法
// 惯用写法:return new ClassName(*this);
// 这会调用上面的拷贝构造函数
Monster* clone() override {
return new Orc(*this);
}
void Show() override {
cout << " -> 兽人: " << name << ", HP: " << hp
<< ", 攻击力: " << *weaponAttr
<< " (内存地址: " << this << ")" << endl;
}
};
class Slime : public Monster {
// 史莱姆比较简单,使用默认拷贝构造即可(如果是浅拷贝安全的)
public:
Monster* clone() override {
return new Slime(*this);
}
void Show() override { cout << " -> 史莱姆分裂了..." << endl; }
};
// ==========================================
// 3. 原型管理器 (Prototype Manager / Factory)
// 可选:用于管理和存储常用的原型
// ==========================================
class MonsterFactory {
private:
unordered_map<string, Monster*> prototypes;
public:
~MonsterFactory() {
for (auto p : prototypes) delete p.second;
}
// 注册原型
void Register(string key, Monster* m) {
prototypes[key] = m;
}
// 生产对象(通过克隆)
Monster* Create(string key) {
if (prototypes.find(key) != prototypes.end()) {
return prototypes[key]->clone(); // 调用原型的 clone
}
return nullptr;
}
};
// ==========================================
// 客户端代码
// ==========================================
int main() {
// 1. 初始化阶段:创建种子(原型)
cout << "=== 游戏初始化:加载原型 ===" << endl;
Monster* orcPrototype = new Orc("精英兽人", 200, 50);
MonsterFactory factory;
factory.Register("Orc", orcPrototype);
factory.Register("Slime", new Slime());
// 2. 战斗阶段:快速刷怪
cout << "\n=== 战斗开始:刷怪 ===" << endl;
// 不需要知道 Orc 的构造细节,直接克隆
Monster* m1 = factory.Create("Orc");
m1->Show();
Monster* m2 = factory.Create("Orc");
m2->Show();
Monster* s1 = factory.Create("Slime");
s1->Show();
delete m1;
delete m2;
delete s1;
return 0;
}
1. 定义
- 原文: 使用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
- 解读: 给我一个“模具”,我照着它复制。我不关心它是怎么 new 出来的,我只管 copy。
2. 解决的问题 (Performance & Decoupling)
- 性能瓶颈 (Performance):
- 如果对象的创建成本(构造函数执行时间)非常高(如读数据库、读文件、复杂计算),而你又需要大量相同或相似的对象。
- 克隆(内存块复制)通常比
new+ 初始化要快得多。
- 解耦 (Decoupling):
- 客户端(Main函数)不需要知道
Orc的具体类名,也不需要知道Orc的构造参数(比如攻击力是50还是100)。它只要拿着 factory 里的原型去clone即可。
- 客户端(Main函数)不需要知道
3. C++ 的核心痛点:深拷贝 vs 浅拷贝
这是 C++ 面试考原型模式时必问的陷阱。
- 浅拷贝 (Shallow Copy):
- 默认的拷贝构造函数是浅拷贝。
- 如果类里有指针(如
int* weaponAttr),浅拷贝只会复制指针的地址。 - 后果: 克隆出来的对象和原对象指向同一块内存。当其中一个对象析构时 (
delete weaponAttr),另一个对象的指针就变成了悬垂指针(Dangling Pointer),再次析构会引发 Double Free 崩溃。
- 深拷贝 (Deep Copy):
- 必须手写拷贝构造函数。
new一块新的内存,把原指针指向的值拷贝过去。- 原则: 原型模式在 C++ 中必须保证深拷贝安全。
4. 符合哪些设计原则?
- A. 依赖倒置原则 (DIP)
- 客户端依赖
Monster接口的clone方法,而不依赖具体的new Orc(...)。
- 客户端依赖
- B. 开闭原则 (OCP)
- 如果想增加一种新怪物
Dragon,只需要实现Dragon并注册到工厂,不需要修改现有刷怪逻辑。
- 如果想增加一种新怪物
5. 原型模式 vs 工厂模式
这是初学者最容易混淆的地方:
| 特性 | 工厂模式 (Factory) | 原型模式 (Prototype) |
|---|---|---|
| 创建方式 | 使用 new 关键字 |
使用 clone() 方法 |
| 初始化过程 | 每次都执行完整的构造函数 | 复制现有内存状态 (跳过复杂初始化) |
| 状态 | 创建出的对象通常是“出厂设置” | 创建出的对象保留了原型运行时的状态 |
| 适用场景 | 对象创建简单,或每次都需要全新状态 | 对象创建昂贵,或需要大量相似对象 |
💡 总结
原型模式的核心就一句话:
当你发现 new 一个对象太慢、太麻烦,或者你根本不知道你要创建的对象的具体类型(只知道它是一个 Monster*)时,不要创建,直接复制 (Clone)。
C++ 记住这个公式:
Prototype Pattern = virtual clone() + Deep Copy Constructor。
浙公网安备 33010602011771号