原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制(克隆)一个已存在的实例(原型)来创建新对象,而无需通过构造函数重新初始化。被复制的实例称为“原型”,新对象的创建通过复制原型的属性实现,避免了复杂的初始化过程。
介绍
核心概念
- 抽象原型(Prototype):
定义克隆接口(通常是clone()
方法),所有具体原型都需实现此接口。 - 具体原型(Concrete Prototype):
实现clone()
方法,通过复制自身创建新对象(需处理深拷贝/浅拷贝)。 - 客户端(Client):
通过调用原型的clone()
方法创建新对象,无需依赖具体类的构造函数。
优点
- 性能优化:跳过复杂的初始化逻辑(如数据库查询、网络请求),直接克隆内存中的对象,提升创建效率。
- 简化创建:无需记忆构造函数参数,通过原型快速生成新对象。
- 动态扩展:运行时可动态添加/替换原型,无需修改现有代码(符合“开闭原则”)。
- 保护原型:克隆操作不影响原始对象,适合“只读原型”场景(如配置模板)。
缺点
- 深拷贝复杂性:若对象包含指针或嵌套对象,需实现深拷贝,否则克隆对象可能与原型共享资源,导致意外修改。
- 原型管理成本:系统中原型过多时,需额外管理(如通过“原型注册表”统一维护)。
适用场景
- 对象初始化成本高:当创建对象需要复杂的计算、数据库查询或网络请求时,克隆现有对象比重新初始化更高效。
- 需要动态生成多种类型对象:例如文档编辑器中可能有文本、图片、表格等元素,通过原型可以统一管理这些类型的创建。
- 避免构造函数的限制:当无法通过构造函数参数精确描述对象(如对象状态复杂)时,克隆是更简单的方式。
- 需要保护原始对象:通过克隆副本进行操作,避免修改原始对象的状态。
实现
以“文档编辑器”为例,通过原型模式快速克隆不同类型的文档元素
#include <iostream>
#include <string>
#include <memory>
// 原型基类:定义克隆接口
class DocumentElement {
public:
virtual ~DocumentElement() = default;
// 纯虚函数:返回自身的克隆体(深拷贝)
virtual std::unique_ptr<DocumentElement> clone() const = 0;
virtual void display() const = 0;
virtual void setContent(const std::string& content) {
m_content = content;
}
protected:
std::string m_content; // 元素内容
};
// 具体原型1:文本元素
class TextElement : public DocumentElement {
public:
// 克隆自身(深拷贝)
std::unique_ptr<DocumentElement> clone() const override {
return std::make_unique<TextElement>(*this); // 利用拷贝构造函数
}
void display() const override {
std::cout << "文本元素: " << m_content << std::endl;
}
};
// 具体原型2:图片元素
class ImageElement : public DocumentElement {
public:
ImageElement() = default;
// 拷贝构造函数(深拷贝资源)
ImageElement(const ImageElement& other)
: DocumentElement(other), m_width(other.m_width), m_height(other.m_height) {}
// 克隆自身
std::unique_ptr<DocumentElement> clone() const override {
return std::make_unique<ImageElement>(*this);
}
void setSize(int width, int height) {
m_width = width;
m_height = height;
}
void display() const override {
std::cout << "图片元素: " << m_content
<< " (" << m_width << "x" << m_height << ")" << std::endl;
}
private:
int m_width = 0; // 图片宽度
int m_height = 0; // 图片高度
};
// 客户端:文档编辑器
class DocumentEditor {
public:
// 从原型克隆新元素
std::unique_ptr<DocumentElement> createElement(const DocumentElement& prototype) {
return prototype.clone(); // 调用原型的克隆方法
}
};
int main() {
DocumentEditor editor;
// 创建原型实例
TextElement textPrototype;
textPrototype.setContent("默认文本");
ImageElement imagePrototype;
imagePrototype.setContent("默认图片路径");
imagePrototype.setSize(800, 600);
// 克隆原型生成新对象
auto text1 = editor.createElement(textPrototype);
text1->setContent("第一章 引言");
text1->display(); // 文本元素: 第一章 引言
auto text2 = editor.createElement(textPrototype);
text2->setContent("第二章 方法");
text2->display(); // 文本元素: 第二章 方法
auto image1 = editor.createElement(imagePrototype);
image1->setContent("fig1.png");
image1->display(); // 图片元素: fig1.png (800x600)
auto image2 = editor.createElement(imagePrototype);
image2->setContent("fig2.png");
image2->setSize(1024, 768);
image2->display(); // 图片元素: fig2.png (1024x768)
return 0;
}
典型应用场景
- 文档编辑器:如 Word 中的“复制粘贴”功能,本质是克隆文本、图片等元素。
- 游戏开发:克隆敌人、道具等重复出现的对象(如批量生成同类型怪物)。
- 数据库连接池:克隆已初始化的连接对象,避免重复建立连接的开销。
- 配置管理:基于默认配置原型,克隆出不同环境的配置对象(开发/测试/生产)。
- GUI 组件库:通过原型快速创建相同样式的按钮、输入框等组件。
优化
1、引入原型注册表(Prototype Registry),统一管理所有原型
2、增强类型安全,避免类型转换错误
3、添加深拷贝的完整性验证
4、支持原型的动态注册与替换
#include <iostream>
#include <string>
#include <memory>
#include <unordered_map>
#include <stdexcept>
#include <typeinfo>
#include <typeindex>
// 原型基类:提供克隆接口和类型标识
class Prototype {
public:
virtual ~Prototype() = default;
virtual std::unique_ptr<Prototype> clone() const = 0;
virtual std::type_index type() const = 0;
// 验证克隆是否成功的钩子方法
virtual bool validateClone() const {
return true; // 默认验证通过
}
};
// 模板基类:自动实现类型标识和克隆接口
template <typename Derived>
class ConcretePrototype : public Prototype {
public:
std::unique_ptr<Prototype> clone() const override {
// 利用派生类的拷贝构造函数进行深拷贝
auto cloneObj = std::make_unique<Derived>(static_cast<const Derived&>(*this));
// 验证克隆完整性
if (!cloneObj->validateClone()) {
throw std::runtime_error("原型克隆失败:对象状态不完整");
}
return cloneObj;
}
std::type_index type() const override {
return typeid(Derived);
}
};
// 原型注册表:管理所有可克隆的原型
class PrototypeRegistry {
public:
// 单例模式:确保全局只有一个注册表
static PrototypeRegistry& getInstance() {
static PrototypeRegistry instance;
return instance;
}
// 注册原型
template <typename T>
void registerPrototype(const T& prototype) {
std::type_index type = typeid(T);
prototypes_[type] = std::make_unique<T>(prototype);
}
// 从注册表克隆对象(类型安全)
template <typename T>
std::unique_ptr<T> clone() const {
std::type_index type = typeid(T);
auto it = prototypes_.find(type);
if (it == prototypes_.end()) {
throw std::invalid_argument("未找到指定类型的原型");
}
// 类型转换并克隆
auto prototype = dynamic_cast<T*>(it->second.get());
if (!prototype) {
throw std::bad_cast();
}
return std::unique_ptr<T>(static_cast<T*>(it->second->clone().release()));
}
// 移除原型
template <typename T>
void unregisterPrototype() {
prototypes_.erase(typeid(T));
}
private:
PrototypeRegistry() = default;
PrototypeRegistry(const PrototypeRegistry&) = delete;
PrototypeRegistry& operator=(const PrototypeRegistry&) = delete;
std::unordered_map<std::type_index, std::unique_ptr<Prototype>> prototypes_;
};
// 具体原型1:文本元素
class TextElement : public ConcretePrototype<TextElement> {
public:
TextElement() = default;
TextElement(const TextElement& other)
: content_(other.content_), fontSize_(other.fontSize_) {}
void setContent(const std::string& content) { content_ = content; }
void setFontSize(int size) { fontSize_ = size; }
void display() const {
std::cout << "文本元素 [字体大小:" << fontSize_ << "]: " << content_ << std::endl;
}
// 验证克隆完整性
bool validateClone() const override {
return !content_.empty() && fontSize_ > 0;
}
private:
std::string content_;
int fontSize_ = 12; // 默认字体大小
};
// 具体原型2:图片元素
class ImageElement : public ConcretePrototype<ImageElement> {
public:
ImageElement() = default;
ImageElement(const ImageElement& other)
: path_(other.path_), width_(other.width_), height_(other.height_) {}
void setPath(const std::string& path) { path_ = path; }
void setSize(int width, int height) {
width_ = width;
height_ = height;
}
void display() const {
std::cout << "图片元素: " << path_
<< " (" << width_ << "x" << height_ << ")" << std::endl;
}
// 验证克隆完整性
bool validateClone() const override {
return !path_.empty() && width_ > 0 && height_ > 0;
}
private:
std::string path_;
int width_ = 0;
int height_ = 0;
};
// 客户端代码
int main() {
try {
// 获取原型注册表实例
auto& registry = PrototypeRegistry::getInstance();
// 注册原型(可在程序初始化时完成)
TextElement textProto;
textProto.setContent("默认文本");
textProto.setFontSize(14);
registry.registerPrototype(textProto);
ImageElement imageProto;
imageProto.setPath("default.png");
imageProto.setSize(800, 600);
registry.registerPrototype(imageProto);
// 从注册表克隆对象
auto text1 = registry.clone<TextElement>();
text1->setContent("优化后的原型模式");
text1->display(); // 文本元素 [字体大小:14]: 优化后的原型模式
auto image1 = registry.clone<ImageElement>();
image1->setPath("example.png");
image1->display(); // 图片元素: example.png (800x600)
// 克隆另一个文本元素
auto text2 = registry.clone<TextElement>();
text2->setContent("类型安全的克隆操作");
text2->setFontSize(16);
text2->display(); // 文本元素 [字体大小:16]: 类型安全的克隆操作
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
优化项
引入原型注册表
- 使用单例模式实现全局唯一的原型注册表
- 支持原型的注册、克隆和移除操作
- 集中管理所有原型,避免原型对象的分散创建
增强类型安全
- 使用
std::type_index
作为原型类型标识 - 模板方法
clone()
确保返回正确类型的对象,避免手动类型转换 - 加入
dynamic_cast
验证,防止类型不匹配错误
- 使用
完善的克隆验证
- 增加
validateClone()
方法验证克隆对象的完整性 - 确保克隆后的对象处于有效状态,避免使用不完整的对象
- 增加
代码复用与扩展性
- 抽象出
ConcretePrototype
模板基类,自动实现克隆和类型接口 - 新原型类型只需继承该模板类,无需重复实现基础功能
- 支持运行时动态注册新原型,符合开闭原则
- 抽象出
错误处理
- 完善的异常处理机制,清晰报告错误原因
- 处理未注册原型、类型转换失败、克隆不完整等异常情况