原型模式及优化
原型模式(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
模板基类,自动实现克隆和类型接口 - 新原型类型只需继承该模板类,无需重复实现基础功能
- 支持运行时动态注册新原型,符合开闭原则
- 抽象出
-
错误处理
- 完善的异常处理机制,清晰报告错误原因
- 处理未注册原型、类型转换失败、克隆不完整等异常情况