1 原型模式的基本概念
原型模式(Prototype Pattern)是一种创建型设计模式,它利用已有对象作为原型,通过复制原型对象来创建新的对象。在这个过程中,不需要重新初始化新对象,而是直接复制原型对象的数据和结构,因此可以显著提高性能。原型模式特别适用于需要创建大量相似对象的情况,或者对象的创建过程非常耗时和资源密集。
原型模式包含三个主要角色:
(1)原型:这是一个实现了克隆方法的接口或抽象类,定义了如何复制对象。
(2)具体原型:这是实现了原型接口的具体类,包含了实际的对象数据。
(3)客户:这是需要创建新对象的类,它通过调用具体原型的克隆方法来创建新对象。
原型模式有两种克隆方式:浅克隆和深克隆。浅克隆只复制对象本身和对象中的值类型字段,对于引用类型字段,只复制引用但不复制引用的对象。而深克隆则复制对象本身和对象中的所有字段,包括引用类型字段引用的对象。
总体而言,原型模式是一种非常有用的设计模式,它可以在不重新初始化对象的情况下创建新的对象,从而提高性能和效率。
1.1 原型模式的应用场景
原型模式在多种场景下都非常有用,以下是一些典型的应用场景:
(1)资源优化场景:当创建一个新对象需要消耗大量资源(如时间、CPU 资源或网络资源)时,可以通过原型模式对已有对象进行复制来获得新对象,从而避免重复消耗资源。
(2)性能和安全要求的场景:在需要高效创建大量相似对象的情况下,原型模式可以显著提高性能。此外,由于对象是通过复制得到的,因此还可以确保对象的安全性,避免被意外修改。
(3)对象状态保存和恢复:原型模式可以方便地保存和恢复对象的状态。通过深克隆方式复制对象并保存其状态,可以在需要时恢复到历史状态,实现撤销操作等。
(4)避免使用分层次的工厂类:当需要避免使用分层次的工厂类来创建分层次的对象时,可以使用原型模式。特别是当类的实例对象只有一个或很少的几个组合状态时,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
(5)对象需要频繁修改的场景:当一个对象需要被多个修改者修改时,可以使用原型模式拷贝多个对象供调用者使用,以避免原始对象被意外修改。
在实际项目中,原型模式经常与工厂方法模式一起使用。通过克隆方法创建一个对象,然后由工厂方法提供给调用者。这种结合使用可以进一步提高对象的创建效率和灵活性。
需要注意的是,在使用原型模式时,需要为每个类配置一个克隆方法。因此,在使用深克隆和浅克隆时需要谨慎选择,以确保对象的正确复制和状态保存。同时,也需要注意线程安全问题,避免在多线程环境下出现竞态条件等问题。
1.2 原型模式的优点和缺点
原型模式的优点主要包括:
(1)性能优势:当需要创建大量相似对象时,通过复制已有对象而非重新初始化新对象,可以显著提高性能。
(2)简化创建过程:对于复杂的对象创建过程,原型模式提供了简化的创建结构。它不需要专门的工厂类来创建产品,而是通过封装在原型类中的克隆方法来实现对象的复制。
(3)可扩展性:原型模式提供了抽象原型类,客户端可以针对抽象原型类进行编程。这样,在增加或减少产品类时,对原有系统没有影响,提高了系统的可扩展性。
(4)支持深拷贝:可以使用深拷贝的方式保存对象的状态,以便在需要的时候使用,如恢复到某一历史状态。这有助于实现撤销操作等。
然而,原型模式也存在一些缺点:
(1)需要为每个类配备克隆方法:这可能会导致代码量增加,并且需要对类的功能进行通盘考虑。对于已有的类进行改造时,需要修改源代码,这违背了“开闭原则”。
(2)深拷贝实现复杂:当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆。这可能会增加实现的复杂性。
(3)潜在的线程安全问题:在多线程环境下,如果没有采取适当的同步措施,原型模式可能会导致竞态条件等问题。
综上所述,在使用原型模式时,需要权衡其优点和缺点,并根据具体场景和需求进行决策。在某些情况下,可能需要结合其他设计模式或采取额外的措施来解决潜在的问题。
2 原型模式的实现
在C++中实现原型模式,通常使用类来定义原型,并为其提供一个克隆方法(一般命名为 clone 或 copy )。
2.1 浅拷贝方式
首先,定义一个抽象的原型基类 Prototype,该基类定义了一个 clone 方法用于创建对象的副本:
#include <iostream>
#include <memory>
// 原型接口
class Prototype
{
public:
virtual ~Prototype() = default;
// 克隆方法,返回对象的副本
virtual std::shared_ptr<Prototype> clone() const = 0;
// 原型的其他方法
virtual void display() const = 0;
};
接下来,定义一个具体的实现类 ConcretePrototype ,它包含了实际的数据(在这个例子中是 m_val)并实现了 clone 方法:
// 具体原型类
class ConcretePrototype : public Prototype
{
public:
ConcretePrototype(int value)
{
m_val = value;
}
// 实现克隆方法
std::shared_ptr<Prototype> clone() const override
{
// 浅拷贝
return std::make_shared<ConcretePrototype>(*this);
}
// 实现显示方法
void display() const override
{
std::cout << "value: " << m_val << std::endl;
}
private:
int m_val;
};
之后,在 main 函数中,就可以创建原型对象,并使用 clone 方法创建它的副本:
// 客户端代码
int main()
{
std::shared_ptr<Prototype> obj1 = std::make_shared<ConcretePrototype>(1);
std::cout << "obj1 ";
obj1->display();
std::shared_ptr<Prototype> obj2 = obj1->clone();
std::cout << "obj2 ";
obj2->display();
return 0;
}
上面代码的输出为:
obj1 value: 1
obj2 value: 1
注意,上面的原型模式由于其成员变量是基础类型,所以使用浅拷贝即可,如果所复制的对象的成员变量中包含了其他对象的引用,则需要使用深拷贝。
2.2 深拷贝方式
首先,定义一个被原型对象所包含的对象类型:
#include <iostream>
#include <memory>
// 一个被原型对象所包含的对象类型
class DependentObject
{
public:
DependentObject(int value) : val(value) {}
void display() const
{
std::cout << "value: " << val << std::endl;
}
public:
int val;
};
然后,像上面样例一样,定义一个抽象的原型基类 Prototype:
// 原型接口
class Prototype
{
public:
virtual ~Prototype() = default;
// 克隆方法,返回对象的深拷贝副本
virtual std::shared_ptr<Prototype> clone() const = 0;
virtual void display() const = 0;
};
接下来,定义一个具体的实现类 ConcretePrototype ,该类实现了深拷贝方式的 clone 方法:
// 具体原型类
class ConcretePrototype : public Prototype
{
public:
ConcretePrototype(std::shared_ptr<DependentObject> dependent)
: dependent(dependent) {}
// 实现克隆方法,进行深拷贝
std::shared_ptr<Prototype> clone() const override
{
// 创建新的DependentObject的深拷贝
auto newDependent = std::make_shared<DependentObject>(*dependent);
// 创建ConcretePrototype的深拷贝
return std::make_shared<ConcretePrototype>(newDependent);
}
void display() const override
{
dependent->display();
}
public:
std::shared_ptr<DependentObject> dependent;
};
之后,在 main 函数中,就可以创建原型对象,并使用 clone 方法创建它的副本:
// 客户端代码
int main()
{
// 创建 DependentObject 对象
auto dependent = std::make_shared<DependentObject>(1);
// 创建原型对象
std::shared_ptr<Prototype> obj1 = std::make_shared<ConcretePrototype>(dependent);
// 克隆原型对象,这将进行深拷贝
std::shared_ptr<Prototype> obj2 = obj1->clone();
// 显示原型和克隆对象
std::cout << "obj1 ";
obj1->display();
std::cout << "obj2 ";
obj2->display();
// 修改原始DependentObject对象
dependent->val = 2;
// 再次显示原型和克隆对象,以验证深拷贝
std::cout << "after modifying the original DependentObject:" << std::endl;
std::cout << "obj1 ";
obj1->display();
std::cout << "obj2 ";
obj2->display();
return 0;
}
上面代码的输出为:
obj1 value: 1
obj2 value: 1
after modifying the original DependentObject:
obj1 value: 2
obj2 value: 1
在上面代码中,创建了一个 ConcretePrototype 实例并克隆了它。然后,修改了原始 DependentObject 实例的值,并再次显示了原型和克隆对象的状态。由于进行了深拷贝,所以克隆对象的状态不受原始对象状态变化的影响。
3 原型模式的应用实例
3.1 实现一个数据库对象缓存系统
在 C++ 中使用原型模式实现一个数据库对象缓存系统,可以创建一个原型管理器类来管理对象的创建和缓存。这个管理器类将负责存储已经创建的对象,并在请求新对象时提供已存在对象的副本,或者在必要时创建新对象。
首先,定义一个数据库对象原型接口:
#include <iostream>
#include <unordered_map>
#include <memory>
#include <string>
// 数据库对象原型接口
class DatabaseObjectPrototype
{
public:
virtual ~DatabaseObjectPrototype() = default;
// 克隆方法,返回对象的副本
virtual std::shared_ptr<DatabaseObjectPrototype> clone() const = 0;
// 从数据库中加载数据的虚拟方法
virtual void loadFromDatabase() = 0;
};
接下来,定义一个具体的实现类 DatabaseObject ,它实现了 clone 方法:
// 具体数据库对象原型类
class DatabaseObject : public DatabaseObjectPrototype
{
public:
DatabaseObject(int id) : m_id(id) {}
// 实现克隆方法
std::shared_ptr<DatabaseObjectPrototype> clone() const override
{
return std::make_shared<DatabaseObject>(*this);
}
// 从数据库中加载数据
void loadFromDatabase() override
{
// 假设从数据库加载数据
std::cout << "Data for object " + std::to_string(m_id) << std::endl;
}
private:
int m_id;
};
然后,定义一个原型管理器类 PrototypeManager ,负责创建和缓存对象,该类管理对象的创建和缓存。它使用一个 unordered_map 来存储已经创建的对象,并在请求新对象时首先检查缓存中是否存在。如果不存在,则创建一个新对象并将其添加到缓存中:
class PrototypeManager
{
private:
// 使用unordered_map作为缓存
std::unordered_map<int, std::shared_ptr<DatabaseObjectPrototype>> cache;
public:
// 获取指定ID的对象,如果缓存中没有则创建新对象
std::shared_ptr<DatabaseObjectPrototype> getObject(int id)
{
// 检查缓存中是否已有对象
if (cache.find(id) != cache.end()) {
return cache[id];
}
// 如果缓存中没有,则创建新对象并添加到缓存中
auto newObj = std::make_shared<DatabaseObject>(id);
newObj->loadFromDatabase(); // 假设每次创建新对象时都需要从数据库加载数据
cache[id] = newObj;
return newObj;
}
// 清空缓存
void clearCache()
{
cache.clear();
}
};
之后,在 main 函数中,就可以创建原型对象,并使用 clone 方法创建它的副本:
// 客户端代码
int main()
{
PrototypeManager manager;
// 获取ID为1的对象
auto obj1 = manager.getObject(1);
// 再次获取ID为1的对象,应该返回缓存中的对象
auto obj2 = manager.getObject(1);
// 清空缓存
manager.clearCache();
// 再次获取ID为 1 的对象,应该重新创建并加载数据
auto obj3 = manager.getObject(1);
return 0;
}
上面代码的输出为:
Data for object 1
Data for object 1
上面代码展示了如何使用 PrototypeManager 来获取对象,并验证了当请求相同 ID 的对象时,系统会返回缓存中的对象副本。此外,还展示了如何清空缓存并重新创建对象。
3.2 实现一个图形绘制工具
原型模式允许创建对象的副本,而无需重新实例化它们。这对于图形绘制工具来说非常有用,因为开发人员可以创建各种图形的原型,然后克隆这些原型来绘制多个相同的图形。
下面是一个简单的样例,展示了如何使用原型模式来创建一个图形绘制工具,其中包含了圆形和矩形的绘制功能:
#include <iostream>
#include <memory>
#include <vector>
// 定义图形接口
class Shape
{
public:
virtual ~Shape() = default;
// 克隆方法,用于创建对象的副本
virtual std::shared_ptr<Shape> clone() const = 0;
// 绘制方法
virtual void draw() const = 0;
};
// 圆形类,实现图形接口
class Circle : public Shape
{
public:
Circle(int radius) : m_radius(radius) {}
void draw() const override
{
std::cout << "drawing a circle with radius: " << m_radius << std::endl;
}
std::shared_ptr<Shape> clone() const override
{
return std::make_shared<Circle>(*this);
}
private:
int m_radius;
};
// 矩形类,实现图形接口
class Rectangle : public Shape
{
public:
Rectangle(int width, int height) : m_width(width), m_height(height) {}
void draw() const override
{
std::cout << "drawing a rectangle with width: " << m_width << " and height: " << m_height << std::endl;
}
std::shared_ptr<Shape> clone() const override
{
return std::make_shared<Rectangle>(*this);
}
private:
int m_width;
int m_height;
};
// 图形工厂类,用于创建和存储图形原型
class ShapeFactory
{
private:
std::vector<std::shared_ptr<Shape>> prototypes;
public:
void registerPrototype(const std::shared_ptr<Shape>& prototype)
{
prototypes.push_back(prototype);
}
std::shared_ptr<Shape> createShape(const std::string& shapeType)
{
for (const auto& prototype : prototypes)
{
if (shapeType == "circle")
{
if (dynamic_cast<const Circle*>(prototype.get()))
{
return prototype->clone();
}
}
else if (shapeType == "rectangle")
{
if (dynamic_cast<const Rectangle*>(prototype.get()))
{
return prototype->clone();
}
}
}
return nullptr;
}
};
int main()
{
// 创建图形原型并注册到工厂
ShapeFactory factory;
factory.registerPrototype(std::make_shared<Circle>(2));
factory.registerPrototype(std::make_shared<Rectangle>(5, 10));
// 使用工厂创建并绘制图形
std::shared_ptr<Shape> shape1 = factory.createShape("circle");
if (shape1)
{
shape1->draw();
}
std::shared_ptr<Shape> shape2 = factory.createShape("rectangle");
if (shape2)
{
shape2->draw();
}
return 0;
}
上面代码的输出为:
drawing a circle with radius: 2
drawing a rectangle with width: 5 and height: 10
在上面代码中,首先定义了一个 Shape 接口,它包含了一个 clone 方法和一个 draw 方法。然后创建了两个实现 Shape 接口的类:Circle 和 Rectangle,它们分别表示圆形和矩形,并实现了 clone 和 draw 方法。
ShapeFactory 类充当了一个原型管理器,它允许注册不同类型的图形原型,并提供了一个方法来创建特定类型的图形对象。上面代码中使用 std::vector 来存储原型对象的智能指针,并通过 createShape 方法来根据类型创建图形对象。
在 main 函数中,创建了一个 ShapeFactory 实例,并向其中注册了一个圆形原型和一个矩形原型。然后,使用工厂来创建并绘制这两种图形。