C++ · 设计模式 (对象创建=Prototype<原型模式>)
1. 概述
GoF分类
原型模式属于创建型设计模式
官方定义
使用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
核心目的
通过克隆已有对象来创建新对象,避免直接使用构造函数带来的性能开销和复杂性
解决的问题
-
需要创建的对象类型由原型实例决定
-
避免创建者与具体产品类的紧耦合
-
当直接创建对象的成本较高时(如需要复杂初始化过程)
C++典型应用
-
游戏开发中的敌人/道具复制
-
文档编辑器中的元素复制
-
需要高性能对象创建的场景
-
复杂对象配置的快速复制
关键特征
-
通过克隆而非构造创建对象
-
原型管理器可管理一组原型
-
支持深拷贝和浅拷贝
-
减少子类数量
2. 核心思想
设计哲学
“不要重新发明轮子” - 通过复制已有对象来创建新对象,而不是每次都从头开始构建
面向对象原则
-
开闭原则:无需修改已有代码即可添加新原型
-
单一职责原则:将克隆逻辑与业务逻辑分离
-
依赖倒置原则:客户端依赖抽象原型而非具体类
C++特殊考虑
-
必须正确处理拷贝语义(深拷贝 vs 浅拷贝)
-
考虑异常安全性
-
使用智能指针管理原型生命周期
-
虚析构函数保证正确销毁
工作流程
3. 适用场景
应该使用原型模式的场景
- 游戏实体复制
// 敌人原型
auto dragonPrototype = std::make_unique<Dragon>();
// 克隆敌人
auto dragon1 = dragonPrototype->clone();
auto dragon2 = dragonPrototype->clone();
- 文档对象复制
// 获取选中的图形原型
auto shapeProto = selectedShape->clone();
// 添加到文档
document.addShape(std::move(shapeProto));
- 配置模板复制
// 获取默认配置
auto defaultConfig = configRegistry.getPrototype("default");
// 创建用户配置
auto userConfig = defaultConfig->clone();
userConfig->setUserName("Alice");
不适合使用原型模式的场景
- 简单对象创建
-
直接构造比克隆更简单高效
-
替代方案:直接使用构造函数
- 需要唯一实例的场景
-
原型模式天然适合复制
-
替代方案:使用单例模式
- 对象状态过于简单
-
克隆开销可能超过构造开销
-
替代方案:工厂方法或直接构造
4. UML类图
5. 结构
模式参与者
Prototype(抽象原型)
-
声明克隆自身的接口
-
通常为抽象基类或接口
-
在C++中使用纯虚函数
ConcretePrototype(具体原型)
-
实现克隆操作
-
可能包含复杂的状态数据
-
决定深拷贝或浅拷贝策略
Client(客户端)
-
通过请求原型克隆新对象
-
无需知道具体原型类
-
管理原型实例的生命周期
协作流程
-
客户端通过原型管理器获取原型实例
-
客户端调用原型的clone()方法
-
具体原型创建自身的精确副本
-
客户端使用克隆得到的新对象
-
当不再需要时销毁克隆对象
6. 代码示例
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
// 抽象原型接口
class Prototype {
public:
virtual ~Prototype() = default;
virtual std::unique_ptr<Prototype> clone() const = 0;
virtual void printInfo() const = 0;
};
// 具体原型A
class ConcretePrototypeA : public Prototype {
public:
ConcretePrototypeA(const std::string& name, int value)
: name_(name), value_(value) {}
std::unique_ptr<Prototype> clone() const override {
// 使用拷贝构造函数实现深拷贝
return std::make_unique<ConcretePrototypeA>(*this);
}
void printInfo() const override {
std::cout << "ConcretePrototypeA: " << name_ << ", " << value_ << "\n";
}
void setName(const std::string& name) { name_ = name; }
void setValue(int value) { value_ = value; }
private:
std::string name_;
int value_;
};
// 具体原型B
class ConcretePrototypeB : public Prototype {
public:
ConcretePrototypeB(float data, std::unique_ptr<int> ptr)
: data_(data), ptr_(std::move(ptr)) {}
// 深拷贝实现
std::unique_ptr<Prototype> clone() const override {
auto newPtr = ptr_ ? std::make_unique<int>(*ptr_) : nullptr;
return std::make_unique<ConcretePrototypeB>(data_, std::move(newPtr));
}
void printInfo() const override {
std::cout << "ConcretePrototypeB: " << data_;
if (ptr_) {
std::cout << ", " << *ptr_;
}
std::cout << "\n";
}
private:
float data_;
std::unique_ptr<int> ptr_;
};
// 原型管理器
class PrototypeRegistry {
public:
void addPrototype(const std::string& key, std::unique_ptr<Prototype> proto) {
prototypes_[key] = std::move(proto);
}
std::unique_ptr<Prototype> clone(const std::string& key) {
if (prototypes_.find(key) != prototypes_.end()) {
return prototypes_[key]->clone();
}
return nullptr;
}
private:
std::unordered_map<std::string, std::unique_ptr<Prototype>> prototypes_;
};
int main() {
// 初始化原型管理器
PrototypeRegistry registry;
// 注册原型
registry.addPrototype("A1", std::make_unique<ConcretePrototypeA>("Original", 42));
registry.addPrototype("B1", std::make_unique<ConcretePrototypeB>(3.14f, std::make_unique<int>(100)));
// 克隆并使用对象
auto cloneA = registry.clone("A1");
if (cloneA) {
cloneA->printInfo();
// 可以修改克隆后的对象
if (auto concreteA = dynamic_cast<ConcretePrototypeA*>(cloneA.get())) {
concreteA->setName("Modified");
concreteA->setValue(99);
concreteA->printInfo();
}
}
auto cloneB = registry.clone("B1");
if (cloneB) {
cloneB->printInfo();
}
// 测试不存在的原型
auto cloneC = registry.clone("C1");
if (!cloneC) {
std::cout << "Prototype C1 not found\n";
}
return 0;
}
7. 运行结果
ConcretePrototypeA: Original, 42
ConcretePrototypeA: Modified, 99
ConcretePrototypeB: 3.14, 100
Prototype C1 not found
输出解释:
-
首先克隆并打印了原型A的初始状态
-
修改克隆后的A对象并打印新状态
-
克隆并打印了原型B的状态
-
尝试克隆不存在的原型C时显示错误信息
8. 应用场景
实际项目案例
C++标准库
-
std::function的复制语义类似于原型模式 -
各种智能指针的拷贝控制
游戏开发
-
Unity引擎的Prefab系统
-
敌人/NPC的批量生成
-
武器/道具的实例化
图形编程
-
图形元素的复制粘贴
-
复杂3D模型的实例化
-
粒子系统的配置模板
行业特定应用
嵌入式系统
-
硬件配置模板的复制
-
通信协议的实例生成
-
节省内存的轻量级复制
高性能计算
-
计算任务的快速复制
-
避免重复初始化开销
-
并行任务的快速生成
9. 变体
主要变体实现
基础变体
// 简单克隆接口
class SimplePrototype {
public:
virtual SimplePrototype* clone() const = 0;
};
模板变体
template <typename T>
class Cloneable {
public:
virtual std::unique_ptr<T> clone() const = 0;
};
class Widget : public Cloneable<Widget> {
public:
std::unique_ptr<Widget> clone() const override {
return std::make_unique<Widget>(*this);
}
};
多态变体
class AbstractShape {
public:
virtual std::unique_ptr<AbstractShape> clone() const = 0;
};
class Circle : public AbstractShape {
std::unique_ptr<AbstractShape> clone() const override {
return std::make_unique<Circle>(*this);
}
};
变体对比表格
| 变体名称 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 基础变体 | 简单直接 | 需要手动管理内存 | 简单项目 |
| 模板变体 | 类型安全 | 模板复杂性 | 需要强类型保证 |
| 多态变体 | 灵活扩展 | 运行时开销 | 复杂继承体系 |
10. 原型模式 vs. 其他模式
与工厂模式的区别
-
工厂模式:通过工厂方法创建新对象
-
原型模式:通过克隆现有对象创建新对象
-
选择依据:当对象创建成本高时选择原型模式
与单例模式的区别
-
单例模式:确保只有一个实例
-
原型模式:专门用于创建多个相似实例
-
组合使用:单例可以作为原型管理器
11. 原型模式的优缺点
优点分析
- 性能优势
-
避免重复初始化开销
-
特别适合创建成本高的对象
-
在C++中可避免虚函数调用开销
- 灵活性
-
运行时动态添加/删除原型
-
客户端与具体类解耦
-
减少子类数量
缺点分析
- 拷贝语义复杂性
-
深拷贝实现可能复杂
-
需要正确处理资源管理
-
C++中需要仔细设计拷贝构造函数
- 继承体系限制
-
每个具体原型必须实现clone()
-
对已有类的修改可能困难
-
可能违反里氏替换原则
12. 什么时候不要使用原型模式?
反对信号
- 对象构造简单
-
直接构造比克隆更高效
-
替代方案:使用构造函数或工厂方法
- 需要唯一实例
-
原型模式天然适合复制
-
替代方案:单例模式
- 对象状态过于复杂
-
深拷贝实现困难
-
替代方案:序列化/反序列化
反模式警示
- 浅拷贝误用
-
导致对象间共享状态
-
正确做法:实现真正的深拷贝
- 过度设计
-
简单场景使用复杂模式
-
正确做法:KISS原则
13. 总结
核心价值
-
提供高效的对象创建机制
-
解耦客户端与具体类
-
在C++中实现资源安全的对象复制
最佳实践
-
明确区分深拷贝和浅拷贝
-
使用智能指针管理原型生命周期
-
考虑使用原型管理器集中管理
-
为clone()方法提供异常安全保证
进阶学习
-
研究拷贝构造函数的高级用法
-
学习C++对象模型与内存管理
-
探索原型模式与其他模式的组合
-
实践项目:实现一个图形编辑器原型系统
更多推荐
所有评论(0)