原型模式及优化
原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制(克隆)一个已存在的实例(原型)来创建新对象,而无需通过构造函数重新初始化。被复制的实例称为“原型”,新对象的创建通过复制原型的属性实现,避免了复杂的初始化过程。
介绍
核心概念
- 抽象原型(Prototype):
定义克隆接口(通常是clone()
方法),所有具体原型都需实现此接口。 - 具体原型(Concrete Prototype):
实现clone()
方法,通过复制自身创建新对象(需处理深拷贝/浅拷贝)。 - 客户端(Client):
通过调用原型的clone()
方法创建新对象,无需依赖具体类的构造函数。
优点
- 性能优化:跳过复杂的初始化逻辑(如数据库查询、网络请求),直接克隆内存中的对象,提升创建效率。
- 简化创建:无需记忆构造函数参数,通过原型快速生成新对象。
- 动态扩展:运行时可动态添加/替换原型,无需修改现有代码(符合“开闭原则”)。
- 保护原型:克隆操作不影响原始对象,适合“只读原型”场景(如配置模板)。
缺点
- 深拷贝复杂性:若对象包含指针或嵌套对象,需实现深拷贝,否则克隆对象可能与原型共享资源,导致意外修改。
- 原型管理成本:系统中原型过多时,需额外管理(如通过“原型注册表”统一维护)。
适用场景
- 对象初始化成本高:当创建对象需要复杂的计算、数据库查询或网络请求时,克隆现有对象比重新初始化更高效。
- 需要动态生成多种类型对象:例如文档编辑器中可能有文本、图片、表格等元素,通过原型可以统一管理这些类型的创建。
- 避免构造函数的限制:当无法通过构造函数参数精确描述对象(如对象状态复杂)时,克隆是更简单的方式。
- 需要保护原始对象:通过克隆副本进行操作,避免修改原始对象的状态。
实现
以“文档编辑器”为例,通过原型模式快速克隆不同类型的文档元素
#include #include #include // 原型基类:定义克隆接口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 #include #include #include #include #include #include // 原型基类:提供克隆接口和类型标识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
模板基类,自动实现克隆和类型接口 - 新原型类型只需继承该模板类,无需重复实现基础功能
- 支持运行时动态注册新原型,符合开闭原则
- 抽象出
-
错误处理
- 完善的异常处理机制,清晰报告错误原因
- 处理未注册原型、类型转换失败、克隆不完整等异常情况