目录
三、C++ 重构实战:用原型模式打造 “秒克隆” 的游戏角色系统
需求:实现一个数据库连接池,池中的连接对象初始化成本高(需要建立网络连接、验证权限),每次从池中获取连接时,克隆一份原型连接,避免多个线程竞争同一份连接。
需求:程序启动时加载全局配置(数据库地址、日志路径、缓存大小等),多个模块需要使用配置,但部分模块需要微调配置(如日志模块需要修改日志级别),通过克隆原型配置,避免修改全局配置影响其他模块。
需求:游戏中有多种地形(平原、山地、森林),每种地形包含地形属性(摩擦力、障碍物)、资源点(矿石、树木),创建地形对象需要加载地形模型和资源配置(耗时),批量生成地图时,通过克隆原型地形快速创建。
需求:系统需要生成多个报表(销售报表、库存报表、用户报表),报表包含相同的格式(标题、表头、页脚),不同的业务数据,创建报表时需要加载模板(耗时),通过克隆原型报表快速生成。

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
引言:为什么创建复杂对象总让你卡性能?

作为 C++ 开发者,你一定写过这样的代码:
// 复杂对象:包含大缓存、配置信息、动态资源的业务对象
class ComplexBusinessObj {
public:
// 构造函数:初始化成本极高(加载配置、申请大内存、连接资源)
ComplexBusinessObj() {
// 1. 加载配置文件(耗时IO)
loadConfig("config.json");
// 2. 申请100MB缓存(耗时内存分配)
cache_ = new char[1024 * 1024 * 100];
// 3. 初始化连接池(耗时网络操作)
initConnectionPool(10);
std::cout << "复杂对象创建完成,耗时300ms" << std::endl;
}
// 业务方法:使用缓存和连接池处理数据
void processData(const std::string& data) {
std::cout << "处理数据:" << data << "(使用缓存和连接池)" << std::endl;
}
private:
char* cache_; // 大缓存(100MB)
std::unordered_map<std::string, std::string> config_; // 配置信息
std::vector<void*> connections_; // 数据库连接池
void loadConfig(const std::string& path) { /* 加载配置逻辑 */ }
void initConnectionPool(int size) { /* 初始化连接池逻辑 */ }
};
// 业务场景:需要创建10个相同配置的复杂对象
int main() {
// 循环创建10个对象,每个耗时300ms,总耗时3秒!
for (int i = 0; i < 10; ++i) {
ComplexBusinessObj* obj = new ComplexBusinessObj();
obj->processData("测试数据" + std::to_string(i));
// ... 后续逻辑
}
return 0;
}
这段代码的痛点,你大概率深有体会:
- 创建成本极高:复杂对象需要加载配置、申请大内存、初始化连接池等耗时操作,重复创建会严重拖慢性能;
- 代码冗余:如果需要创建多个 “配置相同、状态相似” 的对象,只能重复调用构造函数,代码冗余且维护困难;
- 状态复制繁琐:如果已有一个配置好的对象,想创建一个状态相同的新对象,需要手动复制所有成员变量(尤其是指针、容器等复杂成员),容易出错;
- 扩展性差:新增成员变量时,手动复制的代码需要同步修改,违反开放封闭原则。
而原型模式(Prototype Pattern),正是为解决 “复杂对象高效创建” 而生的设计模式:它通过 “克隆已有对象” 替代 “重新创建对象”,让复杂对象的创建成本从 “秒级” 降到 “微秒级”,同时避免重复初始化和手动复制的繁琐。
本文会用 C++ 实战场景 + 可运行代码 + 真实踩坑经验,带你彻底吃透原型模式。从浅克隆到深克隆,从基础实现到工业级应用,读完这篇,你再也不会为 “重复创建复杂对象” 头疼。
一、先搞懂:原型模式到底是什么?

1.1 核心思想:“复制粘贴” 现成对象,而非重新创建
原型模式的核心是:用一个已创建的实例(原型)作为模板,通过克隆(复制)该实例来创建新的对象,而无需重新执行复杂的初始化过程。
用一句大白话解释:原型模式就像 “复印文件”—— 你已经有一份排版好、内容完整的文件(原型对象),想要多份副本时,直接复印(克隆)即可,无需重新打字、排版(重新初始化)。
举个生活化的例子:
- 原型:你电脑里的一份 PPT 模板(已经做好排版、配色、字体);
- 克隆:你右键 “复制” 这份模板,得到一份新 PPT(和原模板完全一致),无需重新设置格式;
- 优势:如果 PPT 模板设置复杂(耗时 1 小时),复制只需 1 秒,效率天差地别。
再看技术场景:
- 原型:一个已经初始化完成的数据库连接池对象(加载了配置、创建了 10 个连接);
- 克隆:通过原型克隆出一个新对象,直接复用已有连接池配置和连接,无需重新初始化;
- 优势:创建原型耗时 1 秒,克隆只需 1 微秒,性能提升 1000 倍。
1.2 原型模式的 2 个关键概念:浅克隆 vs 深克隆
这是原型模式的核心区别,也是最容易踩坑的地方,必须彻底搞懂:
浅克隆(Shallow Clone)
- 定义:只复制对象本身和基本数据类型成员(int、double、string 等),对于指针、引用、动态分配的资源(如
new的数组、容器),只复制指针的地址,不复制指针指向的实际数据; - 形象理解:复制文件时,只创建 “快捷方式”(指向原文件地址),不复制文件内容;
- 优点:克隆速度快,开销小;
- 缺点:多个克隆对象共享同一份动态资源,一个对象修改资源会影响其他对象,甚至导致野指针(原对象销毁后,克隆对象的指针悬空)。
深克隆(Deep Clone)
- 定义:不仅复制对象本身和基本数据类型成员,还会递归复制指针、引用指向的所有动态资源(如
new的数组、嵌套对象、容器中的指针); - 形象理解:复制文件时,复制整个文件内容,生成独立的新文件,和原文件无关联;
- 优点:克隆对象完全独立,修改资源不会影响其他对象,安全稳定;
- 缺点:克隆速度比浅克隆慢,开销较大(需要复制所有嵌套资源)。
1.3 原型模式的 3 个核心角色(极简)
原型模式是所有设计模式中角色最少的,只有 3 个,各司其职:
- 抽象原型(Abstract Prototype):声明克隆方法的抽象接口(如 C++ 中的纯虚函数
clone()),是所有原型类的父类; - 具体原型(Concrete Prototype):实现抽象原型的克隆方法,负责实际的克隆逻辑(浅克隆或深克隆);
- 客户端(Client):不需要知道对象的创建细节,只需调用原型对象的克隆方法,即可创建新对象。
1.4 原型模式 vs 工厂模式:别搞混了!
很多开发者分不清原型模式和工厂模式,这里用一句话和表格明确区别:
- 核心区别:工厂模式通过 “构造函数创建新对象”,原型模式通过 “克隆已有对象创建新对象”;
- 工厂模式:适合 “从零创建简单对象”,不依赖已有实例;
- 原型模式:适合 “复制复杂对象”,依赖已有实例(原型),避免重复初始化。
| 维度 | 原型模式 | 工厂模式(工厂方法 / 抽象工厂) |
|---|---|---|
| 核心目标 | 高效克隆复杂对象,避免重复初始化 | 创建新对象,封装创建逻辑 |
| 依赖条件 | 必须有已存在的原型对象 | 无需已有对象,通过参数 / 工厂创建 |
| 性能开销 | 浅克隆快,深克隆较慢 | 取决于构造函数复杂度,复杂对象创建慢 |
| 适用场景 | 复杂对象、重复创建相似对象、对象池 | 简单对象、产品族切换、创建逻辑复杂 |
举个例子:
- 工厂模式:你去蛋糕店,告诉店员 “要一个巧克力蛋糕”(参数),店员从零开始制作(构造函数);
- 原型模式:你带了一个做好的巧克力蛋糕(原型),让店员按这个蛋糕克隆 10 个(复制已有蛋糕),无需重新烘焙。
1.5 什么时候该用原型模式?4 个典型场景
原型模式不是万能的,以下 4 种场景下它是最优解:
- 复杂对象创建成本高:对象需要初始化大量动态资源(如大缓存、数据库连接、网络请求),重复创建耗时;
- 需要批量生成相似对象:多个对象的配置、状态大部分相同,只需微调部分属性(如 10 个游戏角色,只有名字和等级不同,其他属性一致);
- 对象池场景:对象池中的对象需要复用,从池子里取出时克隆一份,避免多个线程竞争同一份资源;
- 避免手动复制繁琐:对象包含多个嵌套成员(如
vector<vector<string>>、指针数组),手动复制代码冗余且容易出错。
简单说:当你觉得 “创建一个对象太麻烦,不如复制一个现成的” 时,就该考虑原型模式。
二、C++ 反例实战:不用原型模式的痛点有多痛?

为了让你直观感受原型模式的价值,我们先实现一个 “不用原型模式” 的复杂对象创建场景,看看它的性能和代码问题。
2.1 需求背景
实现一个 “游戏角色创建系统”,核心需求:
- 游戏角色(
GameCharacter)包含基础属性(名字、等级、血量)、装备列表(vector<Equipment*>,装备有名称和攻击力)、技能列表(vector<Skill*>,技能有名称和伤害); - 创建角色时,需要初始化默认装备(3 件)和默认技能(2 个),初始化过程耗时(模拟加载配置、网络请求);
- 游戏开局需要创建 10 个玩家角色,属性大部分相同(只有名字和等级不同)。
2.2 反例代码:不用原型模式,重复创建复杂对象
#include <iostream>
#include <string>
#include <vector>
#include <chrono> // 用于计时
// 装备类(嵌套对象,动态创建)
class Equipment {
public:
Equipment(const std::string& name, int attack) : name_(name), attack_(attack) {
// 模拟装备初始化耗时(如加载装备模型)
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
void show() const {
std::cout << "装备:" << name_ << "(攻击力:" << attack_ << ")" << std::endl;
}
private:
std::string name_;
int attack_;
};
// 技能类(嵌套对象,动态创建)
class Skill {
public:
Skill(const std::string& name, int damage) : name_(name), damage_(damage) {
// 模拟技能初始化耗时(如加载技能特效)
std::this_thread::sleep_for(std::chrono::milliseconds(15));
}
void show() const {
std::cout << "技能:" << name_ << "(伤害:" << damage_ << ")" << std::endl;
}
private:
std::string name_;
int damage_;
};
// 游戏角色类(复杂对象,包含多个动态嵌套对象)
class GameCharacter {
public:
// 构造函数:初始化成本极高(嵌套对象+耗时操作)
GameCharacter(const std::string& name, int level) : name_(name), level_(level), hp_(level * 100) {
std::cout << "创建角色:" << name_ << "(开始初始化...)" << std::endl;
// 初始化默认装备(3件,每件耗时10ms)
equipments_.push_back(new Equipment("铁剑", 50));
equipments_.push_back(new Equipment("皮甲", 20));
equipments_.push_back(new Equipment("布鞋", 10));
// 初始化默认技能(2个,每个耗时15ms)
skills_.push_back(new Skill("劈砍", 80));
skills_.push_back(new Skill("防御", 0));
// 模拟额外耗时操作(如加载角色模型、网络同步)
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::cout << "角色" << name_ << "创建完成,耗时:" << (3*10 + 2*15 + 50) << "ms" << std::endl;
}
// 析构函数:释放动态资源
~GameCharacter() {
for (auto eq : equipments_) delete eq;
for (auto sk : skills_) delete sk;
}
// 显示角色信息
void showInfo() const {
std::cout << "\n=== 角色信息 ===" << std::endl;
std::cout << "名字:" << name_ << ",等级:" << level_ << ",血量:" << hp_ << std::endl;
std::cout << "装备列表:" << std::endl;
for (auto eq : equipments_) eq->show();
std::cout << "技能列表:" << std::endl;
for (auto sk : skills_) sk->show();
}
private:
std::string name_; // 名字(唯一,需要修改)
int level_; // 等级(可能不同)
int hp_; // 血量(等级*100,依赖等级)
std::vector<Equipment*> equipments_; // 装备列表(动态资源)
std::vector<Skill*> skills_; // 技能列表(动态资源)
};
// 客户端:创建10个游戏角色
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 循环创建10个角色,每个角色都重复初始化装备和技能(冗余!)
for (int i = 0; i < 10; ++i) {
std::string name = "玩家" + std::to_string(i+1);
GameCharacter* character = new GameCharacter(name, 10); // 等级默认10级
character->showInfo();
delete character; // 销毁角色
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "\n创建10个角色总耗时:" << duration.count() << "秒" << std::endl;
return 0;
}
2.3 反例代码的 4 大致命问题
这段代码能实现功能,但运行后你会发现痛点极其明显:
- 性能极差:每个角色创建耗时 120ms(310+215+50),10 个角色总耗时 1.2 秒以上,游戏开局加载慢,影响用户体验;
- 代码冗余:10 个角色的装备和技能完全相同,却重复执行初始化逻辑(加载装备、技能),做了大量无用功;
- 资源浪费:重复申请和释放动态资源(装备、技能),内存分配和释放的开销大;
- 扩展性差:如果新增默认装备或技能,所有创建角色的代码都要修改,违反开放封闭原则。
这就是不用原型模式的噩梦 —— 复杂对象的重复创建,不仅耗时,还导致代码冗余、维护困难。而原型模式,只需创建一个原型角色,后续通过克隆快速生成新角色,完美解决这些问题。
三、C++ 重构实战:用原型模式打造 “秒克隆” 的游戏角色系统

针对反例的痛点,我们用原型模式重构,核心思路是 “创建一个原型角色,后续通过克隆生成新角色,只修改差异化属性”:
- 定义抽象原型接口
IPrototype,声明clone()纯虚方法; - 让
GameCharacter继承IPrototype,实现浅克隆和深克隆方法; - 客户端先创建一个原型角色(初始化一次装备和技能),后续通过
clone()快速生成新角色,只修改名字、等级等差异化属性。
3.1 重构后的完整代码(浅克隆 + 深克隆)
3.1.1 抽象原型接口
// IPrototype.h(抽象原型接口)
#ifndef I_PROTOTYPE_H
#define I_PROTOTYPE_H
// 抽象原型:声明克隆方法
class IPrototype {
public:
virtual ~IPrototype() = default;
// 纯虚克隆方法:返回自身的克隆体(智能指针避免内存泄漏)
virtual std::unique_ptr<IPrototype> clone() const = 0;
};
#endif // I_PROTOTYPE_H
3.1.2 具体原型:游戏角色(支持浅克隆和深克隆)
// GameCharacter.h(具体原型:游戏角色)
#ifndef GAME_CHARACTER_H
#define GAME_CHARACTER_H
#include "IPrototype.h"
#include <string>
#include <vector>
#include <memory>
// 装备类(嵌套对象)
class Equipment {
public:
Equipment(const std::string& name, int attack) : name_(name), attack_(attack) {}
~Equipment() = default;
// 深克隆方法(用于角色的深克隆)
std::unique_ptr<Equipment> clone() const {
return std::make_unique<Equipment>(name_, attack_);
}
void show() const {
std::cout << "装备:" << name_ << "(攻击力:" << attack_ << ")" << std::endl;
}
// setter:用于修改克隆后的装备属性(可选)
void setAttack(int attack) { attack_ = attack; }
private:
std::string name_;
int attack_;
};
// 技能类(嵌套对象)
class Skill {
public:
Skill(const std::string& name, int damage) : name_(name), damage_(damage) {}
~Skill() = default;
// 深克隆方法
std::unique_ptr<Skill> clone() const {
return std::make_unique<Skill>(name_, damage_);
}
void show() const {
std::cout << "技能:" << name_ << "(伤害:" << damage_ << ")" << std::endl;
}
private:
std::string name_;
int damage_;
};
// 具体原型:游戏角色(继承抽象原型)
class GameCharacter : public IPrototype {
public:
GameCharacter(const std::string& name, int level) : name_(name), level_(level), hp_(level * 100) {
// 初始化默认装备和技能(只执行一次,原型创建时)
initDefaultEquipments();
initDefaultSkills();
}
// 析构函数:释放动态资源
~GameCharacter() = default; // 智能指针自动释放,无需手动delete
// 浅克隆实现:只复制对象本身和智能指针(不复制指针指向的资源)
std::unique_ptr<IPrototype> cloneShallow() const {
// 调用拷贝构造函数,智能指针会浅拷贝(共享资源)
return std::make_unique<GameCharacter>(*this);
}
// 深克隆实现:递归复制所有嵌套资源(装备、技能)
std::unique_ptr<IPrototype> cloneDeep() const override {
// 1. 创建新的角色对象(复制基础属性)
auto newCharacter = std::make_unique<GameCharacter>(name_, level_);
newCharacter->hp_ = hp_;
// 2. 深克隆装备列表(递归复制每个装备)
for (const auto& eq : equipments_) {
newCharacter->equipments_.push_back(eq->clone());
}
// 3. 深克隆技能列表(递归复制每个技能)
for (const auto& sk : skills_) {
newCharacter->skills_.push_back(sk->clone());
}
return newCharacter;
}
// 重写clone():默认使用深克隆(根据需求选择)
std::unique_ptr<IPrototype> clone() const override {
return cloneDeep();
}
// 修改差异化属性(克隆后调用)
void setName(const std::string& name) { name_ = name; }
void setLevel(int level) {
level_ = level;
hp_ = level * 100; // 血量随等级变化
}
// 显示角色信息
void showInfo() const {
std::cout << "\n=== 角色信息 ===" << std::endl;
std::cout << "名字:" << name_ << ",等级:" << level_ << ",血量:" << hp_ << std::endl;
std::cout << "装备列表:" << std::endl;
for (const auto& eq : equipments_) eq->show();
std::cout << "技能列表:" << std::endl;
for (const auto& sk : skills_) sk->show();
}
private:
// 初始化默认装备(只执行一次)
void initDefaultEquipments() {
equipments_.push_back(std::make_unique<Equipment>("铁剑", 50));
equipments_.push_back(std::make_unique<Equipment>("皮甲", 20));
equipments_.push_back(std::make_unique<Equipment>("布鞋", 10));
}
// 初始化默认技能(只执行一次)
void initDefaultSkills() {
skills_.push_back(std::make_unique<Skill>("劈砍", 80));
skills_.push_back(std::make_unique<Skill>("防御", 0));
}
private:
std::string name_;
int level_;
int hp_;
// 用智能指针管理嵌套资源,避免内存泄漏
std::vector<std::unique_ptr<Equipment>> equipments_;
std::vector<std::unique_ptr<Skill>> skills_;
};
#endif // GAME_CHARACTER_H
3.1.3 客户端:通过克隆创建 10 个角色
// main.cpp
#include <iostream>
#include <chrono>
#include "GameCharacter.h"
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 1. 创建原型角色(只初始化一次装备和技能,耗时120ms)
std::cout << "=== 创建原型角色 ===" << std::endl;
auto prototype = std::make_unique<GameCharacter>("原型角色", 10);
prototype->showInfo();
// 2. 克隆10个新角色(每个克隆耗时微秒级)
std::cout << "\n=== 克隆10个新角色 ===" << std::endl;
for (int i = 0; i < 10; ++i) {
// 调用克隆方法创建新角色
auto newCharacter = std::unique_ptr<GameCharacter>(
dynamic_cast<GameCharacter*>(prototype->clone().release())
);
// 修改差异化属性(名字和等级)
newCharacter->setName("玩家" + std::to_string(i+1));
newCharacter->setLevel(10 + i); // 等级从10到19递增
// 显示新角色信息
newCharacter->showInfo();
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "\n创建1个原型+克隆10个角色总耗时:" << duration.count() << "秒" << std::endl;
return 0;
}
3.2 编译与运行说明
3.2.1 编译命令(GCC)
g++ -std=c++11 main.cpp -o prototype_game -lpthread
- 依赖:C++11 及以上标准(支持智能指针、
dynamic_cast); - 编译环境:Linux/GCC、Windows/MinGW、Mac/Clang 均可。
3.2.2 运行结果
=== 创建原型角色 ===
=== 角色信息 ===
名字:原型角色,等级:10,血量:1000
装备列表:
装备:铁剑(攻击力:50)
装备:皮甲(攻击力:20)
装备:布鞋(攻击力:10)
技能列表:
技能:劈砍(伤害:80)
技能:防御(伤害:0)
=== 克隆10个新角色 ===
=== 角色信息 ===
名字:玩家1,等级:10,血量:1000
装备列表:
装备:铁剑(攻击力:50)
装备:皮甲(攻击力:20)
装备:布鞋(攻击力:10)
技能列表:
技能:劈砍(伤害:80)
技能:防御(伤害:0)
...(省略8个角色)...
=== 角色信息 ===
名字:玩家10,等级:19,血量:1900
装备列表:
装备:铁剑(攻击力:50)
装备:皮甲(攻击力:20)
装备:布鞋(攻击力:10)
技能列表:
技能:劈砍(伤害:80)
技能:防御(伤害:0)
创建1个原型+克隆10个角色总耗时:0.125秒
3.3 重构后的核心优势(对比反例)
重构后的代码完全符合原型模式,解决了反例的所有问题:
- 性能爆炸提升:总耗时从 1.2 秒以上降到 0.125 秒左右,性能提升近 10 倍(克隆耗时可忽略);
- 避免重复初始化:装备和技能只在原型创建时初始化一次,后续克隆无需重复加载,减少无用功;
- 代码简洁高效:客户端无需关心角色的创建细节,只需调用
clone(),再修改差异化属性,逻辑清晰; - 安全稳定:使用深克隆,每个角色的装备和技能完全独立,修改一个角色的装备不会影响其他角色;
- 扩展性强:新增默认装备或技能时,只需修改原型的初始化逻辑,克隆角色自动继承,无需修改客户端代码。
3.4 浅克隆 vs 深克隆效果对比
为了让你更直观感受两者的区别,我们修改客户端代码,测试浅克隆的问题:
// 测试浅克隆的问题
void testShallowCloneProblem() {
// 创建原型角色
auto prototype = std::make_unique<GameCharacter>("原型", 10);
// 浅克隆新角色
auto clone = std::unique_ptr<GameCharacter>(
dynamic_cast<GameCharacter*>(prototype->cloneShallow().release())
);
// 原型角色修改装备攻击力
prototype->equipments_[0]->setAttack(100);
std::cout << "=== 原型角色装备 ===" << std::endl;
prototype->equipments_[0]->show(); // 攻击力100
std::cout << "=== 浅克隆角色装备 ===" << std::endl;
clone->equipments_[0]->show(); // 攻击力也变成100(共享资源,问题!)
}
运行结果:
=== 原型角色装备 ===
装备:铁剑(攻击力:100)
=== 浅克隆角色装备 ===
装备:铁剑(攻击力:100)
可见,浅克隆的角色和原型共享装备资源,原型修改装备会影响克隆角色。而深克隆不会有这个问题,克隆角色的装备完全独立。
四、扩展实战:原型模式的 4 个经典应用场景

原型模式的核心价值是 “高效克隆复杂对象”,在 C++ 开发中,凡是需要 “批量生成相似复杂对象” 的场景,都能发挥其优势。下面通过 4 个真实场景,展示模式的灵活性。
4.1 场景 1:对象池(连接池、线程池)
需求:实现一个数据库连接池,池中的连接对象初始化成本高(需要建立网络连接、验证权限),每次从池中获取连接时,克隆一份原型连接,避免多个线程竞争同一份连接。
原型模式实现:
// 抽象原型:数据库连接
class IDbConnection : public IPrototype {
public:
virtual ~IDbConnection() = default;
virtual void connect() = 0; // 连接数据库(初始化)
virtual void executeSql(const std::string& sql) = 0; // 执行SQL
virtual std::unique_ptr<IPrototype> clone() const override = 0;
};
// 具体原型:MySQL连接
class MysqlConnection : public IDbConnection {
public:
MysqlConnection(const std::string& host, int port, const std::string& user, const std::string& pwd)
: host_(host), port_(port), user_(user), pwd_(pwd) {}
// 初始化连接(耗时操作)
void connect() override {
std::cout << "MySQL连接初始化:" << host_ << ":" << port_ << "(耗时200ms)" << std::endl;
// 模拟网络连接、权限验证
std::this_thread::sleep_for(std::chrono::milliseconds(200));
isConnected_ = true;
}
void executeSql(const std::string& sql) override {
if (isConnected_) {
std::cout << "执行SQL:" << sql << std::endl;
} else {
std::cerr << "连接未初始化" << std::endl;
}
}
// 深克隆:创建独立的连接(实际中可能复用连接配置,重新建立连接)
std::unique_ptr<IPrototype> clone() const override {
auto newConn = std::make_unique<MysqlConnection>(host_, port_, user_, pwd_);
newConn->isConnected_ = this->isConnected_;
return newConn;
}
private:
std::string host_;
int port_;
std::string user_;
std::string pwd_;
bool isConnected_ = false;
};
// 对象池:数据库连接池
class DbConnectionPool {
public:
// 初始化池:创建原型连接并初始化
DbConnectionPool(int poolSize, const std::string& host, int port, const std::string& user, const std::string& pwd) {
// 创建原型连接
auto prototype = std::make_unique<MysqlConnection>(host, port, user, pwd);
prototype->connect(); // 初始化一次
// 克隆原型,填充对象池
for (int i = 0; i < poolSize; ++i) {
pool_.push_back(std::unique_ptr<MysqlConnection>(
dynamic_cast<MysqlConnection*>(prototype->clone().release())
));
}
std::cout << "连接池初始化完成,池大小:" << poolSize << std::endl;
}
// 从池获取连接(克隆一份,避免竞争)
std::unique_ptr<MysqlConnection> getConnection() {
std::lock_guard<std::mutex> lock(mtx_);
if (pool_.empty()) {
return nullptr;
}
auto conn = std::move(pool_.back());
pool_.pop_back();
// 克隆一份返回给客户端,原连接放回池(或直接返回克隆体)
auto cloneConn = std::unique_ptr<MysqlConnection>(
dynamic_cast<MysqlConnection*>(conn->clone().release())
);
pool_.push_back(std::move(conn)); // 原连接放回池
return cloneConn;
}
private:
std::vector<std::unique_ptr<MysqlConnection>> pool_;
std::mutex mtx_; // 线程安全锁
};
// 测试代码
int main() {
// 初始化连接池(1个原型+克隆4个连接,池大小5)
DbConnectionPool pool(5, "localhost", 3306, "root", "123456");
// 从池获取连接并使用
auto conn1 = pool.getConnection();
if (conn1) {
conn1->executeSql("SELECT * FROM users");
}
auto conn2 = pool.getConnection();
if (conn2) {
conn2->executeSql("INSERT INTO orders VALUES('ORDER001', 199.9)");
}
return 0;
}
运行结果:
MySQL连接初始化:localhost:3306(耗时200ms)
连接池初始化完成,池大小:5
执行SQL:SELECT * FROM users
执行SQL:INSERT INTO orders VALUES('ORDER001', 199.9)
优势:连接池只需初始化一次原型连接,后续通过克隆快速生成新连接,避免重复建立网络连接,提升性能和并发能力。
4.2 场景 2:配置对象克隆(多模块共享配置)
需求:程序启动时加载全局配置(数据库地址、日志路径、缓存大小等),多个模块需要使用配置,但部分模块需要微调配置(如日志模块需要修改日志级别),通过克隆原型配置,避免修改全局配置影响其他模块。
原型模式实现:
// 配置项:日志配置(嵌套对象)
class LogConfig {
public:
LogConfig(const std::string& path, const std::string& level) : path_(path), level_(level) {}
// 深克隆
std::unique_ptr<LogConfig> clone() const {
return std::make_unique<LogConfig>(path_, level_);
}
// setter
void setLevel(const std::string& level) { level_ = level; }
// getter
std::string getPath() const { return path_; }
std::string getLevel() const { return level_; }
private:
std::string path_; // 日志路径
std::string level_; // 日志级别(info/warn/error)
};
// 配置项:数据库配置(嵌套对象)
class DbConfig {
public:
DbConfig(const std::string& host, int port, const std::string& user, const std::string& pwd)
: host_(host), port_(port), user_(user), pwd_(pwd) {}
std::unique_ptr<DbConfig> clone() const {
return std::make_unique<DbConfig>(host_, port_, user_, pwd_);
}
private:
std::string host_;
int port_;
std::string user_;
std::string pwd_;
};
// 全局配置(具体原型)
class GlobalConfig : public IPrototype {
public:
GlobalConfig(const LogConfig& logConfig, const DbConfig& dbConfig, int cacheSize)
: logConfig_(std::make_unique<LogConfig>(logConfig)),
dbConfig_(std::make_unique<DbConfig>(dbConfig)),
cacheSize_(cacheSize) {}
// 深克隆:复制所有嵌套配置
std::unique_ptr<IPrototype> clone() const override {
auto newConfig = std::make_unique<GlobalConfig>(
*logConfig_, *dbConfig_, cacheSize_
);
return newConfig;
}
// 获取配置(返回克隆体,避免外部修改原配置)
std::unique_ptr<GlobalConfig> getClone() const {
return std::unique_ptr<GlobalConfig>(
dynamic_cast<GlobalConfig*>(clone().release())
);
}
// 修改日志级别(只影响当前配置对象)
void setLogLevel(const std::string& level) {
logConfig_->setLevel(level);
}
// 打印配置
void print() const {
std::cout << "=== 配置信息 ===" << std::endl;
std::cout << "日志路径:" << logConfig_->getPath() << ",级别:" << logConfig_->getLevel() << std::endl;
std::cout << "缓存大小:" << cacheSize_ << "MB" << std::endl;
}
private:
std::unique_ptr<LogConfig> logConfig_;
std::unique_ptr<DbConfig> dbConfig_;
int cacheSize_; // 缓存大小(MB)
};
// 测试代码
int main() {
// 1. 创建原型配置(全局唯一,启动时加载)
LogConfig logProto("/var/log/app.log", "info");
DbConfig dbProto("localhost", 3306, "root", "123456");
GlobalConfig protoConfig(logProto, dbProto, 100);
std::cout << "原型配置:" << std::endl;
protoConfig.print();
// 2. 日志模块克隆配置,修改日志级别为warn
auto logModuleConfig = protoConfig.getClone();
logModuleConfig->setLogLevel("warn");
std::cout << "\n日志模块配置(修改级别):" << std::endl;
logModuleConfig->print();
// 3. 缓存模块克隆配置,不修改(使用默认)
auto cacheModuleConfig = protoConfig.getClone();
std::cout << "\n缓存模块配置(默认):" << std::endl;
cacheModuleConfig->print();
// 验证原型配置未被修改
std::cout << "\n原型配置(未被修改):" << std::endl;
protoConfig.print();
return 0;
}
运行结果:
原型配置:
=== 配置信息 ===
日志路径:/var/log/app.log,级别:info
缓存大小:100MB
日志模块配置(修改级别):
=== 配置信息 ===
日志路径:/var/log/app.log,级别:warn
缓存大小:100MB
缓存模块配置(默认):
=== 配置信息 ===
日志路径:/var/log/app.log,级别:info
缓存大小:100MB
原型配置(未被修改):
=== 配置信息 ===
日志路径:/var/log/app.log,级别:info
缓存大小:100MB
优势:多个模块通过克隆原型配置获得独立的配置对象,修改自身配置不会影响全局原型和其他模块,灵活且安全。
4.3 场景 3:游戏地图对象批量生成
需求:游戏中有多种地形(平原、山地、森林),每种地形包含地形属性(摩擦力、障碍物)、资源点(矿石、树木),创建地形对象需要加载地形模型和资源配置(耗时),批量生成地图时,通过克隆原型地形快速创建。
原型模式实现:
// 资源点(嵌套对象)
class Resource {
public:
Resource(const std::string& name, int count) : name_(name), count_(count) {}
std::unique_ptr<Resource> clone() const {
return std::make_unique<Resource>(name_, count_);
}
void show() const {
std::cout << "资源:" << name_ << "(数量:" << count_ << ")" << std::endl;
}
private:
std::string name_; // 资源名称(矿石/树木)
int count_; // 资源数量
};
// 地形(具体原型)
class Terrain : public IPrototype {
public:
Terrain(const std::string& type, float friction, const std::vector<Resource>& resources)
: type_(type), friction_(friction) {
// 初始化资源(模拟加载模型耗时)
std::cout << "创建" << type_ << "原型(耗时300ms)" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
for (const auto& res : resources) {
resources_.push_back(res.clone());
}
}
// 深克隆
std::unique_ptr<IPrototype> clone() const override {
auto newTerrain = std::make_unique<Terrain>(type_, friction_, std::vector<Resource>());
newTerrain->resources_.clear();
for (const auto& res : resources_) {
newTerrain->resources_.push_back(res->clone());
}
return newTerrain;
}
// 修改地形摩擦力(克隆后微调)
void setFriction(float friction) { friction_ = friction; }
// 显示地形信息
void show() const {
std::cout << "\n=== 地形信息 ===" << std::endl;
std::cout << "类型:" << type_ << ",摩擦力:" << friction_ << std::endl;
std::cout << "资源点:" << std::endl;
for (const auto& res : resources_) {
res->show();
}
}
private:
std::string type_; // 地形类型(平原/山地/森林)
float friction_; // 摩擦力(影响移动速度)
std::vector<std::unique_ptr<Resource>> resources_; // 资源点
};
// 测试代码
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 1. 创建3种地形原型
std::vector<Resource> plainResources = {{"矿石", 10}, {"树木", 20}};
auto plainProto = std::make_unique<Terrain>("平原", 0.5f, plainResources);
std::vector<Resource> mountainResources = {{"矿石", 30}, {"树木", 5}};
auto mountainProto = std::make_unique<Terrain>("山地", 0.8f, mountainResources);
// 2. 克隆生成10个地形(5个平原,5个山地)
std::cout << "\n=== 克隆生成地图地形 ===" << std::endl;
for (int i = 0; i < 5; ++i) {
auto plain = std::unique_ptr<Terrain>(dynamic_cast<Terrain*>(plainProto->clone().release()));
plain->setFriction(0.5f + i * 0.1f); // 微调摩擦力
plain->show();
}
for (int i = 0; i < 5; ++i) {
auto mountain = std::unique_ptr<Terrain>(dynamic_cast<Terrain*>(mountainProto->clone().release()));
mountain->setFriction(0.8f + i * 0.1f);
mountain->show();
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "\n创建2个原型+克隆10个地形总耗时:" << duration.count() << "秒" << std::endl;
return 0;
}
运行结果:
创建平原原型(耗时300ms)
创建山地原型(耗时300ms)
=== 克隆生成地图地形 ===
=== 地形信息 ===
类型:平原,摩擦力:0.5
资源点:
资源:矿石(数量:10)
资源:树木(数量:20)
...(省略8个地形)...
=== 地形信息 ===
类型:山地,摩擦力:1.2
资源点:
资源:矿石(数量:30)
资源:树木(数量:5)
创建2个原型+克隆10个地形总耗时:0.61秒
优势:地形原型只创建一次,克隆生成 10 个地形耗时可忽略,大幅提升地图生成效率,避免重复加载模型。
4.4 场景 4:批量生成报表(相似格式,不同数据)
需求:系统需要生成多个报表(销售报表、库存报表、用户报表),报表包含相同的格式(标题、表头、页脚),不同的业务数据,创建报表时需要加载模板(耗时),通过克隆原型报表快速生成。
原型模式实现:
// 报表数据(嵌套对象,不同报表数据不同)
class ReportData {
public:
ReportData(const std::vector<std::vector<std::string>>& data) : data_(data) {}
std::unique_ptr<ReportData> clone() const {
return std::make_unique<ReportData>(data_);
}
void setData(const std::vector<std::vector<std::string>>& data) { data_ = data; }
const std::vector<std::vector<std::string>>& getData() const { return data_; }
private:
std::vector<std::vector<std::string>> data_; // 二维数据(行×列)
};
// 报表(具体原型)
class Report : public IPrototype {
public:
Report(const std::string& title, const std::vector<std::string>& headers)
: title_(title), headers_(headers) {
// 加载报表模板(模拟耗时IO)
std::cout << "加载" << title << "模板(耗时200ms)" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
data_ = std::make_unique<ReportData>(std::vector<std::vector<std::string>>());
}
// 深克隆
std::unique_ptr<IPrototype> clone() const override {
auto newReport = std::make_unique<Report>(title_, headers_);
newReport->data_ = data_->clone();
return newReport;
}
// 设置报表数据(克隆后填充)
void setData(const std::vector<std::vector<std::string>>& data) {
data_->setData(data);
}
// 生成报表
void generate() const {
std::cout << "\n==================== " << title_ << " ====================" << std::endl;
// 打印表头
for (const auto& header : headers_) {
std::cout << std::setw(15) << header;
}
std::cout << std::endl;
// 打印数据
for (const auto& row : data_->getData()) {
for (const auto& col : row) {
std::cout << std::setw(15) << col;
}
std::cout << std::endl;
}
std::cout << "========================================================" << std::endl;
}
private:
std::string title_; // 报表标题
std::vector<std::string> headers_; // 表头
std::unique_ptr<ReportData> data_; // 报表数据
};
// 测试代码
int main() {
auto start = std::chrono::high_resolution_clock::now();
// 1. 创建报表原型(加载模板)
std::vector<std::string> salesHeaders = {"日期", "产品", "销售额", "销量"};
auto salesProto = std::make_unique<Report>("销售报表", salesHeaders);
std::vector<std::string> stockHeaders = {"产品", "库存数量", "库存位置", "预警阈值"};
auto stockProto = std::make_unique<Report>("库存报表", stockHeaders);
// 2. 克隆报表,填充数据
// 销售报表数据
std::vector<std::vector<std::string>> salesData = {
{"2025-05-20", "手机", "19990", "10"},
{"2025-05-20", "耳机", "2990", "20"},
{"2025-05-20", "键盘", "1590", "15"}
};
auto salesReport = std::unique_ptr<Report>(dynamic_cast<Report*>(salesProto->clone().release()));
salesReport->setData(salesData);
salesReport->generate();
// 库存报表数据
std::vector<std::vector<std::string>> stockData = {
{"手机", "50", "仓库A1区", "10"},
{"耳机", "30", "仓库A2区", "5"},
{"键盘", "20", "仓库B1区", "3"}
};
auto stockReport = std::unique_ptr<Report>(dynamic_cast<Report*>(stockProto->clone().release()));
stockReport->setData(stockData);
stockReport->generate();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::cout << "\n创建2个原型+克隆2个报表总耗时:" << duration.count() << "秒" << std::endl;
return 0;
}
运行结果:
加载销售报表模板(耗时200ms)
加载库存报表模板(耗时200ms)
==================== 销售报表 ====================
日期 产品 销售额 销量
2025-05-20 手机 19990 10
2025-05-20 耳机 2990 20
2025-05-20 键盘 1590 15
========================================================
==================== 库存报表 ====================
产品 库存数量 库存位置 预警阈值
手机 50 仓库A1区 10
耳机 30 仓库A2区 5
键盘 20 仓库B1区 3
========================================================
创建2个原型+克隆2个报表总耗时:0.41秒
优势:报表模板只加载一次,克隆报表时只需填充数据,避免重复加载模板,提升报表生成效率,且格式统一,修改模板时只需修改原型。
五、C++ 实战避坑指南:原型模式的 5 个常见误区

原型模式看似简单,但在 C++ 开发中,很多开发者会因忽视细节导致克隆失败、内存泄漏或逻辑错误。下面列出 5 个典型误区及避坑方案。
误区 1:混淆浅克隆和深克隆,导致资源共享问题
问题代码:
class BadPrototype {
public:
BadPrototype() { data_ = new int[100]; }
// 浅克隆(默认拷贝构造)
BadPrototype(const BadPrototype& other) : data_(other.data_) {}
// 克隆方法返回浅克隆
std::unique_ptr<BadPrototype> clone() const {
return std::make_unique<BadPrototype>(*this);
}
private:
int* data_; // 动态数组,浅克隆会共享
};
问题分析:
浅克隆导致多个对象共享data_指向的动态数组,一个对象修改数组会影响其他对象,原对象销毁后,克隆对象的data_变成野指针。
避坑方案:
- 包含动态资源(指针、嵌套对象、容器)的对象,必须使用深克隆;
- 深克隆时,递归复制所有嵌套资源,确保克隆对象完全独立。
误区 2:克隆方法返回类型错误,导致切片问题
问题代码:
class AbstractProto {
public:
virtual AbstractProto clone() const = 0; // 返回值为基类对象
};
class ConcreteProto : public AbstractProto {
public:
AbstractProto clone() const override {
return ConcreteProto(*this); // 切片:只复制基类部分,丢失子类属性
}
};
问题分析:
克隆方法返回基类对象,会发生 “对象切片”,克隆后的对象丢失子类特有的成员变量和方法。
避坑方案:
- 克隆方法返回智能指针(
std::unique_ptr<AbstractProto>),避免切片; - 客户端通过
dynamic_cast将基类指针转换为子类指针,安全访问子类属性。
误区 3:忘记克隆嵌套对象,导致深克隆不彻底
问题代码:
class NestedObj { /* 嵌套对象 */ };
class Proto : public IPrototype {
public:
std::unique_ptr<IPrototype> clone() const override {
auto newProto = std::make_unique<Proto>(*this);
// 忘记克隆nestedObj_,导致浅克隆嵌套对象
return newProto;
}
private:
std::unique_ptr<NestedObj> nestedObj_;
};
问题分析:
深克隆时只复制了外层对象,忘记递归克隆嵌套对象,导致嵌套对象仍然是浅克隆,共享资源。
避坑方案:
- 深克隆时,遍历所有成员变量,尤其是指针、智能指针、容器,确保每个嵌套资源都被克隆;
- 给嵌套对象单独实现
clone()方法,在外部对象的克隆方法中调用。
误区 4:使用默认拷贝构造替代克隆方法,导致灵活性不足
问题代码:
class Proto {
public:
// 用拷贝构造替代克隆方法
Proto copy() const { return *this; }
};
// 客户端
Proto proto;
Proto clone = proto.copy(); // 只能浅拷贝,无法灵活选择浅/深克隆
问题分析:
默认拷贝构造是固定的浅拷贝,无法根据需求切换浅克隆和深克隆,且不支持多态(子类拷贝构造无法覆盖基类)。
避坑方案:
- 显式声明
clone()方法,支持多态克隆; - 在
clone()方法中灵活实现浅克隆或深克隆,客户端可根据需求调用。
误区 5:克隆对象时未重置状态,导致状态混乱
问题代码:
class GameRole : public IPrototype {
public:
GameRole() : hp_(100), isDead_(false) {}
void attack() { hp_ -= 20; if (hp_ <= 0) isDead_ = true; }
std::unique_ptr<IPrototype> clone() const override {
return std::make_unique<GameRole>(*this); // 克隆时复制了isDead_状态
}
};
// 客户端
auto proto = std::make_unique<GameRole>();
proto->attack(); // 原型hp=80,未死亡
auto clone = proto->clone(); // 克隆对象hp=80,不是初始状态
问题分析:
克隆对象时复制了原型的所有状态(包括临时状态如血量、是否死亡),而客户端可能需要克隆 “初始状态” 的对象。
避坑方案:
- 克隆对象后,根据需求重置临时状态(如血量、连接状态、计时器);
- 区分 “配置属性”(如装备、技能)和 “临时状态”(如血量、位置),克隆时只复制配置属性,临时状态初始化。
六、总结:原型模式的核心与实战建议

6.1 核心要点提炼
- 核心思想:通过克隆已有原型对象,快速创建新对象,避免重复初始化复杂资源;
- 两大克隆方式:浅克隆(快、共享资源)、深克隆(慢、完全独立),根据场景选择;
- 3 个核心角色:抽象原型(声明
clone())、具体原型(实现克隆逻辑)、客户端(调用clone()); - 核心价值:提升复杂对象创建性能、避免代码冗余、支持批量生成相似对象;
- 适用场景:复杂对象、对象池、配置克隆、批量生成相似对象。
6.2 C++ 实战建议
-
抽象原型设计:
- 用纯虚函数
clone()声明克隆接口,返回std::unique_ptr<AbstractPrototype>,避免切片; - 抽象原型类必须有虚析构函数,确保子类正确销毁。
- 用纯虚函数
-
具体原型实现:
- 包含动态资源(指针、嵌套对象)时,必须实现深克隆;
- 深克隆时,递归复制所有嵌套资源,可给嵌套对象单独实现
clone()方法; - 克隆后根据需求重置临时状态(如连接状态、血量)。
-
客户端使用:
- 先创建原型对象,初始化一次复杂资源;
- 后续通过
clone()快速生成新对象,只修改差异化属性; - 用
dynamic_cast将克隆后的基类指针转换为子类指针,安全访问子类方法。
-
与其他模式结合:
- 结合单例模式:原型对象设计为单例,确保全局只有一个原型;
- 结合工厂模式:用工厂管理原型对象,客户端通过工厂获取原型并克隆;
- 结合对象池模式:对象池存储原型对象,获取对象时克隆,避免竞争。
6.3 最后一句话
原型模式的本质,是 “复用已有对象的资源和配置,高效创建新对象”。它不会让你的代码行数减少,但会让复杂对象的创建从 “耗时费力” 变得 “秒级克隆”,尤其在需要批量生成相似对象的场景中,性能提升极为明显。
记住:原型模式不是 “复制粘贴代码”,而是 “复制对象的资源和状态”。合理使用浅克隆和深克隆,既能提升性能,又能保证代码安全稳定。

被折叠的 条评论
为什么被折叠?



