C++设计模式之创建型模式:原型模式(Prototype)
原型模式(Prototype)是创建型设计模式的一种,它通过复制现有对象(原型)来创建新对象,而无需重新初始化,从而简化复杂对象的创建过程并提高效率。这种模式特别适合创建成本高(如初始化步骤多、资源消耗大)或配置复杂的对象。
一、核心思想与角色
原型模式的核心是“通过克隆生成新对象”,避免了重复的初始化逻辑。其核心角色如下:
角色名称 | 核心职责 |
---|---|
抽象原型(Prototype) | 声明克隆方法的接口(通常是纯虚函数clone() ),所有具体原型都需实现该接口。 |
具体原型(Concrete Prototype) | 实现clone() 方法,通过复制自身创建新对象(深拷贝或浅拷贝)。 |
客户端(Client) | 通过调用原型的clone() 方法创建新对象,无需知道具体类名。 |
关键优势:当对象创建复杂时,克隆比重新new
并初始化更高效;同时,客户端可动态生成新对象,无需与具体类耦合。
二、实现示例(文档复制场景)
假设我们需要创建多种文档(如文本文档、表格文档),这些文档包含复杂的格式和内容配置。使用原型模式通过克隆快速创建新文档:
#include <iostream>
#include <string>
#include <vector>
#include <memory>// 1. 抽象原型:文档
class Document {
public:// 声明克隆方法(返回自身的副本)virtual Document* clone() const = 0;// 文档操作virtual void setContent(const std::string& content) { m_content = content; }virtual void addFormat(const std::string& format) { m_formats.push_back(format); }virtual void showInfo() const = 0;virtual ~Document() = default;protected:std::string m_content; // 内容std::vector<std::string> m_formats; // 格式配置(如字体、颜色等)
};// 2. 具体原型1:文本文档
class TextDocument : public Document {
public:// 实现克隆方法:深拷贝自身Document* clone() const override {TextDocument* copy = new TextDocument();copy->m_content = this->m_content; // 复制内容copy->m_formats = this->m_formats; // 复制格式(深拷贝vector)return copy;}void showInfo() const override {std::cout << "文本文档 - 内容: " << m_content << std::endl;std::cout << "格式: ";for (const auto& f : m_formats) {std::cout << f << " ";}std::cout << std::endl;}
};// 2. 具体原型2:表格文档
class SpreadsheetDocument : public Document {
private:int m_rowCount; // 表格特有属性:行数int m_colCount; // 列数public:SpreadsheetDocument(int rows = 0, int cols = 0) : m_rowCount(rows), m_colCount(cols) {}// 实现克隆方法:复制所有属性(包括特有属性)Document* clone() const override {SpreadsheetDocument* copy = new SpreadsheetDocument();copy->m_content = this->m_content;copy->m_formats = this->m_formats;copy->m_rowCount = this->m_rowCount; // 复制表格特有属性copy->m_colCount = this->m_colCount;return copy;}void setRowCol(int rows, int cols) {m_rowCount = rows;m_colCount = cols;}void showInfo() const override {std::cout << "表格文档 - 内容: " << m_content << std::endl;std::cout << "行列: " << m_rowCount << "x" << m_colCount << std::endl;std::cout << "格式: ";for (const auto& f : m_formats) {std::cout << f << " ";}std::cout << std::endl;}
};// 客户端代码:通过克隆创建新文档
int main() {// 1. 创建原型文档(复杂初始化)TextDocument* textProto = new TextDocument();textProto->setContent("原型文本文档");textProto->addFormat("字体: 宋体");textProto->addFormat("字号: 12");SpreadsheetDocument* sheetProto = new SpreadsheetDocument();sheetProto->setContent("原型表格文档");sheetProto->setRowCol(10, 5);sheetProto->addFormat("边框: 实线");sheetProto->addFormat("对齐: 居中");// 2. 克隆原型创建新文档(无需重新配置)Document* textCopy1 = textProto->clone();textCopy1->setContent("克隆的文本文档1"); // 修改内容,不影响原型Document* textCopy2 = textProto->clone();textCopy2->setContent("克隆的文本文档2");textCopy2->addFormat("颜色: 红色"); // 新增格式Document* sheetCopy = sheetProto->clone();sheetCopy->setContent("克隆的表格文档");sheetCopy->setRowCol(8, 4); // 修改表格行列// 3. 展示所有文档std::cout << "=== 原型文档 ===" << std::endl;textProto->showInfo();sheetProto->showInfo();std::cout << "\n=== 克隆文档 ===" << std::endl;textCopy1->showInfo();textCopy2->showInfo();sheetCopy->showInfo();// 4. 释放资源delete textProto;delete sheetProto;delete textCopy1;delete textCopy2;delete sheetCopy;return 0;
}
三、代码解析
-
抽象原型(Document):
定义了克隆接口clone()
和文档通用操作(setContent()
、addFormat()
),包含所有文档共有的属性(内容、格式)。 -
具体原型:
TextDocument
:实现clone()
方法,通过深拷贝复制自身的内容和格式。SpreadsheetDocument
:除复制通用属性外,还克隆表格特有的行列属性,确保克隆对象的独立性。
-
克隆逻辑:
关键是实现深拷贝(而非浅拷贝),确保克隆对象与原型对象完全独立(修改克隆对象不会影响原型)。例如,m_formats
是vector
,直接赋值会复制所有元素(深拷贝);若包含指针成员,则需手动复制指针指向的内容。 -
客户端使用:
客户端只需通过原型的clone()
方法创建新对象,无需知道具体文档类型(如TextDocument
),只需依赖Document
接口,符合依赖倒置原则。
四、深拷贝与浅拷贝
原型模式的核心是“正确克隆对象”,需区分两种拷贝方式:
-
浅拷贝:仅复制对象本身及基本类型成员,指针成员仅复制地址(与原型共享内存)。
风险:修改克隆对象的指针成员会影响原型,可能导致内存泄漏。 -
深拷贝:不仅复制对象本身,还递归复制所有指针成员指向的内容(完全独立的副本)。
实现:在clone()
中手动为指针成员分配新内存并复制数据,或使用智能指针(如std::unique_ptr
)管理动态资源。
示例(含指针成员的深拷贝):
class MyClass : public Prototype {
private:int* data; // 指针成员public:MyClass(int val) : data(new int(val)) {}// 深拷贝实现MyClass* clone() const override {MyClass* copy = new MyClass(0);copy->data = new int(*this->data); // 复制指针指向的内容return copy;}~MyClass() { delete data; } // 释放资源
};
五、适用场景与优势
适用场景
- 当对象创建成本高(如需要读取配置文件、数据库连接),克隆比重新创建更高效。
- 当需要动态生成多种相似对象(如不同配置的文档、游戏角色),且不想硬编码具体类。
- 当对象的结构复杂,初始化步骤多,希望简化创建过程时。
核心优势
- 高效性:克隆避免了重复的初始化逻辑,尤其适合创建成本高的对象。
- 灵活性:客户端可通过克隆动态生成新对象,无需与具体类耦合。
- 简化创建:无需记住复杂的创建参数,直接基于原型修改即可。
六、与其他创建型模式的区别
模式 | 核心差异点 |
---|---|
原型模式 | 通过克隆现有对象创建新对象,适合复杂对象的快速复制。 |
工厂方法 | 通过子类决定创建哪种对象,适合产品类型固定但需扩展的场景。 |
建造者模式 | 分步构建复杂对象,关注构建流程和部件组合,适合配置差异大的对象。 |
单例模式 | 确保对象唯一,与原型模式的“复制多实例”目标相反。 |
七、实践建议
- 优先深拷贝:除非明确需要共享资源,否则
clone()
方法应实现深拷贝,避免对象间的意外影响。 - 结合工厂模式:可创建“原型工厂”管理多个原型,客户端通过工厂获取克隆对象(如
PrototypeFactory::getClone("text")
)。 - 使用智能指针:在现代C++中,用
std::unique_ptr
或std::shared_ptr
管理克隆对象,避免内存泄漏。 - 标记接口:抽象原型可继承一个空接口(如
IPrototype
),仅用于标识该类支持克隆。
原型模式的核心价值在于“通过复制实现高效、灵活的对象创建”。当系统中存在大量相似对象或创建成本高的对象时,使用原型模式能显著提升性能并简化代码,是创建型模式中解决“对象复制”问题的最佳方案。