原型模式是一种创建型设计模式,其核心思想是:通过一个已有的实例作为原型,调用它的克隆接口来创建新的对象,而不是使用常规的构造函数重新初始化。在C++中,这一模式通常借助虚函数与拷贝构造函数共同实现,既保留了多态特性,又避免了重复配置对象的成本。

为什么需要原型模式
在不少业务场景中,创建一个对象的过程非常昂贵。例如对象需要从配置文件读取数据、建立网络连接或者预计算大量中间结果。如果每次都通过new加初始化函数来生成,不仅代码重复,而且性能损耗明显。此时若内存中已经存在一个配置完整的同类对象,直接复制它会高效得多。
另一个典型情况是对象种类在运行时才能确定。假设系统中有一组派生类,调用方并不清楚具体类型,但仍希望拿到一个同类型的新实例。普通工厂在这里会依赖类型判断,而原型模式只需要统一调用clone方法,由对象自己决定如何复制自身,调用方完全解耦。
基础实现:基类定义克隆接口
在C++里实现原型模式,第一步是抽象出一个带有纯虚clone函数的基类。clone的返回类型通常是基类的智能指针或裸指针,内部实际调用派生类的拷贝构造。这样上层代码持有基类指针,也能得到正确的子类副本。
下面给出一个最简结构。注意基类析构函数应声明为虚函数,否则通过基类指针删除派生对象时行为未定义。clone方法则强制每个具体类提供自己的复制逻辑。
#include <iostream>
#include <memory>
class Prototype {
public:
virtual ~Prototype() = default;
virtual std::unique_ptr<Prototype> clone() const = 0;
virtual void show() const = 0;
};
class ConcreteProduct : public Prototype {
public:
ConcreteProduct(int value) : data(value) {}
// 拷贝构造用于克隆
ConcreteProduct(const ConcreteProduct& other) : data(other.data) {}
std::unique_ptr<Prototype> clone() const override {
return std::make_unique<ConcreteProduct>(*this);
}
void show() const override {
std::cout << "ConcreteProduct data: " << data << std::endl;
}
private:
int data;
};
深拷贝与浅拷贝的陷阱
当类中含有指针或资源句柄时,编译器生成的默认拷贝构造只做浅拷贝,也就是复制指针地址而非指向的内容。若原型对象和克隆对象共享同一块堆内存,任一方析构都会使另一方悬空。因此含有资源的类必须在clone路径上实现深拷贝。
以下示例展示了一个管理动态数组的类。其拷贝构造重新分配内存并逐元素复制,从而保证clone出来的对象完全独立。使用std::vector可自动获得深拷贝语义,但若手动管理内存则必须写清楚。
#include <algorithm>
class BufferProto : public Prototype {
public:
BufferProto(const int* src, size_t len) : size(len) {
data = new int[size];
std::copy(src, src + size, data);
}
// 深拷贝构造
BufferProto(const BufferProto& other) : size(other.size) {
data = new int[size];
std::copy(other.data, other.data + size, data);
}
~BufferProto() override { delete[] data; }
std::unique_ptr<Prototype> clone() const override {
return std::make_unique<BufferProto>(*this);
}
void show() const override {
for (size_t i = 0; i < size; ++i) std::cout << data[i] << ' ';
std::cout << std::endl;
}
private:
int* data;
size_t size;
};
结合注册表管理原型
实际工程中,常把多个原型对象放到一个注册表里,用字符串或枚举作为键。客户端传入键即可拿到对应原型并克隆,无需知道具体类名。这种方式进一步降低了创建逻辑与具体类型的耦合。
下面示例实现一个简单的原型管理器。它内部保存基类指针映射,提供registerProto和create两个方法。create先查表再调用clone,如果键不存在则返回空指针。
#include <string>
#include <unordered_map>
class ProtoManager {
public:
void registerProto(const std::string& key, std::unique_ptr<Prototype> proto) {
table[key] = std::move(proto);
}
std::unique_ptr<Prototype> create(const std::string& key) const {
auto it = table.find(key);
if (it == table.end()) return nullptr;
return it->second->clone();
}
private:
std::unordered_map<std::string, std::unique_ptr<Prototype>> table;
};
与构造函数、工厂模式的对比
直接使用构造函数创建对象,代码直观但重复初始化成本高,且调用方依赖具体类。工厂模式把创建封装起来,但仍需在工厂内写分支或映射来new具体类型。原型模式则把复制能力下放到对象自身,新增类型时只需实现clone,不用改工厂逻辑。
不过原型模式也有局限。如果对象的内部状态极其复杂、含有无法简单复制的资源如文件锁、线程句柄,克隆就可能失效或引发错误。此时应配合其他模式,或在clone里做特殊处理,比如重新打开资源而非复制句柄。
完整可运行示例
把前面的组件组合起来,就能看到通过原型创建新对象的完整流程。下面代码注册两个原型,再通过管理器克隆并打印,验证副本独立性。
int main() {
ProtoManager mgr;
mgr.registerProto("product", std::make_unique<ConcreteProduct>(42));
int arr[] = {1, 2, 3};
mgr.registerProto("buffer", std::make_unique<BufferProto>(arr, 3));
auto p1 = mgr.create("product");
p1->show();
auto b1 = mgr.create("buffer");
b1->show();
auto p2 = p1->clone();
p2->show();
return 0;
}
通过上述结构,C++中的原型模式既利用了语言自身的拷贝语义,又借助多态保证了扩展性。只要处理好深拷贝与资源管理,它便是批量生成相似对象时的实用方案。