软件设计模式|软件设计模式笔记|原型模式(Prototype)
原型模式
定义
原型模式也是一种创建型模式,它用来复制一个现有的对象,而不是通过实例化创建一个新的对象。用一句话总结就是,当 new 一个对象很复杂或者开销很大(比如需要访问数据库或者文件系统),可以通过复制一个已经存在的实例来创建新对象,从而提高性能。
其中,“已存在的实例”就是指原型对象。
原型模式用来解决的问题
-
性能问题:当创建一个对象的成本十分昂贵时,例如需要访问数据库、进行复杂的网络请求、设计大量计算或者I/O操作时,使用原型模式可以通过复制现有对象来避免这些开销,从而提高性能。每次都通过 new 来初始化会严重影响性能。如果能有一个“模板”对象,后续的对象直接从这个模板复制,就可以绕过昂贵的初始化过程。
-
解耦和灵活性:当系统需要动态地创建和配置对象,而不想在代码中硬编码具体的类时。客户端代码可以与具体的类解耦,只需与一个抽象的原型接口打交道,通过这个接口克隆出所需的对象。
结构
原型模式的结构通常包含一下几个部分
-
Prototype 接口: (原型接口/抽象类)
- 声明一个克隆自身的方法,通常命名为 clone()。这个接口定义了所有原型对象必须实现的克隆操作。
- 在C++中,这通常是一个抽象基类,包含一个纯虚的 clone() 方法。
-
ConcretePrototype 类:(具体原型类)
- 实现 Prototype 接口,提供具体的克隆逻辑。每个具体原型类都需要实现 clone() 方法,返回一个新的对象实例,该实例是当前对象的副本。
-
Client 类: (客户端)
- 持有一个原型对象的引用。
- 当需要新对象时,请求原型对象克隆自身,从而得到一个新的、一模一样的对象。
- 客户端可以对克隆出的新对象进行修改,而不会影响到原始的原型对象。
示例
我们用一个经典场景举例。假设我们有一个图形库,其中包含圆形Circle()和矩形Rectangle(),创建这些对象可能会有一些初始设置,我们需要根据不同场景复制不同图形对象。
#include <iostream>
#include <string>
#include <vector>
// 1. Prototype (原型抽象基类)
class Shape {
public:
Shape() = default;
Shape(const Shape& source) { // 拷贝构造函数
this->x = source.x;
this->y = source.y;
this->color = source.color;
}
virtual ~Shape() {}
// 纯虚的 clone 方法
virtual Shape* clone() const = 0;
void setPosition(int x, int y) {
this->x = x;
this->y = y;
}
virtual void draw() const {
std::cout << "Drawing a shape at (" << x << ", " << y << ") with color " << color << std::endl;
}
protected:
int x = 0, y = 0;
std::string color = "black";
};
// 2. ConcretePrototype (具体原型类 - Circle)
class Circle : public Shape {
public:
Circle() = default;
Circle(int radius, const std::string& color) : radius(radius) {
this->color = color;
}
// 拷贝构造函数,用于 clone
Circle(const Circle& source) : Shape(source) {
this->radius = source.radius;
}
// 实现 clone 方法
Shape* clone() const override {
return new Circle(*this); // 核心:调用拷贝构造函数
}
void draw() const override {
std::cout << "Drawing a circle with radius " << radius
<< " at (" << x << ", " << y << ") with color " << color << std::endl;
}
private:
int radius = 0;
};
// 2. ConcretePrototype (具体原型类 - Rectangle)
class Rectangle : public Shape {
public:
Rectangle() = default;
Rectangle(int width, int height, const std::string& color) : width(width), height(height) {
this->color = color;
}
// 拷贝构造函数,用于 clone
Rectangle(const Rectangle& source) : Shape(source) {
this->width = source.width;
this->height = source.height;
}
// 实现 clone 方法
Shape* clone() const override {
return new Rectangle(*this);
}
void draw() const override {
std::cout << "Drawing a rectangle with width " << width << " and height " << height
<< " at (" << x << ", " << y << ") with color " << color << std::endl;
}
private:
int width = 0, height = 0;
};
// 3. Client (客户端)
int main() {
// 创建原型对象
Circle* originalCircle = new Circle(10, "Red");
originalCircle->setPosition(50, 50);
Rectangle* originalRectangle = new Rectangle(20, 40, "Blue");
originalRectangle->setPosition(100, 100);
std::cout << "--- Original Prototypes ---" << std::endl;
originalCircle->draw();
originalRectangle->draw();
// 从原型克隆新对象
Shape* clonedCircle = originalCircle->clone();
Shape* clonedRectangle = originalRectangle->clone();
// 修改克隆对象的状态,不会影响原型
clonedCircle->setPosition(60, 60);
clonedCircle->draw();
clonedRectangle->setPosition(120, 150);
clonedRectangle->draw();
std::cout << "\n--- Originals are Unchanged ---" << std::endl;
originalCircle->draw();
originalRectangle->draw();
// 清理内存
delete originalCircle;
delete originalRectangle;
delete clonedCircle;
delete clonedRectangle;
return 0;
}
代码讲解
- Shape 是抽象原型,定义了 clone() 接口。
- Circle() 和 Rectangle() 是具体原型,它们实现了 clone() 方法。
- clone() 的实现非常简洁,就是 return new ConcreteType(*this);。它利用了 C++ 的拷贝构造函数来完成成员变量的复制。
- main 函数作为客户端,先创建好原型实例 originalCircle和 originalRectangle。之后需要新的圆或矩形时,直接调用 clone() 方法,而不是 new Circle(…)。
- 克隆出的对象 clonedCircle 和原始对象 originalCircle 是两个完全独立的对象,修改一个不会影响另一个。
深拷贝VS浅拷贝
上述例子中,我们所有成员对象都是值类型(int,string),默认函数拷贝类型是浅拷贝,但是如果成员对象中包含指针或引用,那么情况就变得复杂了。
-
浅拷贝 (Shallow Copy):只复制指针的值,不复制指针所指向的内容。这会导致克隆对象和原型对象共享同一个内部资源。当一个对象修改这个资源时,另一个对象也会受到影响。更严重的是,当其中一个对象被销毁并释放资源时,另一个对象会持有一个悬挂指针,导致程序崩溃。
-
深拷贝 (Deep Copy):不仅复制指针的值,还会复制指针所指向的内容,为克隆对象分配一块新的内存。这样,克隆对象和原型对象各自拥有独立的内部资源,互不影响。
深拷贝的实现
我们给Shape()类添加一个对象Style(),这个对象Style()通过指针持有。
#include <iostream>
#include <string>
#include <memory> // For smart pointers
// 假设这是一个复杂的样式对象
class Style {
public:
Style(const std::string& fill) : fillColor(fill) {}
// 拷贝构造
Style(const Style& other) : fillColor(other.fillColor) {
std::cout << "Style deep copy constructor called." << std::endl;
}
void setFillColor(const std::string& color) {
fillColor = color;
}
void print() const {
std::cout << "Style(FillColor: " << fillColor << ")";
}
private:
std::string fillColor;
};
// 原型接口
class Shape {
public:
// 构造函数,接管 Style 对象所有权
Shape(Style* style) : style(style) {}
// 深拷贝的拷贝构造函数
Shape(const Shape& other) {
// 深拷贝关键点:为 style 创建一个新的副本
this->style = new Style(*other.style);
}
virtual ~Shape() {
delete style; // 需要手动管理内存
}
virtual Shape* clone() const = 0;
void showStyle() const {
style->print();
std::cout << std::endl;
}
void changeFillColor(const std::string& color) {
style->setFillColor(color);
}
protected:
Style* style;
};
// 具体原型
class Circle : public Shape {
public:
Circle(int r, Style* s) : Shape(s), radius(r) {}
// 拷贝构造函数,调用基类的拷贝构造来处理深拷贝
Circle(const Circle& other) : Shape(other), radius(other.radius) {}
Shape* clone() const override {
return new Circle(*this); // 同样调用拷贝构造
}
private:
int radius;
};
int main() {
// 创建原型
Circle* originalCircle = new Circle(10, new Style("Red"));
std::cout << "Original: ";
originalCircle->showStyle();
// 克隆
Shape* clonedCircle = originalCircle->clone();
std::cout << "Cloned: ";
clonedCircle->showStyle();
// 修改克隆对象的内部状态
std::cout << "\n--- Modifying cloned object's style ---" << std::endl;
clonedCircle->changeFillColor("Green");
std::cout << "Original after modification: ";
originalCircle->showStyle(); // 原型不受影响
std::cout << "Cloned after modification: ";
clonedCircle->showStyle(); // 克隆对象已改变
delete originalCircle;
delete clonedCircle;
return 0;
}
代码讲解
-
在 Shape() 的拷贝构造函数中,我们没有简单地写 this->style = other.style; (浅拷贝),而是写了 this->style = new Style(*other.style);。这行代码为新的 Shape 对象创建了一个全新的 Style 对象,内容与原对象的一样,这就是深拷贝。
-
Circle 的 方法依然是clone() ,它会隐式调用Circle 的拷贝构造函数,而 Circle 的拷贝构造函数又会调用基类Shape 的拷贝构造函数,从而正确地完成了Style 对象的深拷贝。
进阶用法
- 使用原型管理器/注册表
- 在实际应用中,我们通常不会让客户端直接持有具体原型类的实例。更好的做法是创建一个管理器(通常是单例),用来存储和管理一系列原型对象。客户端通过一个键(如字符串ID)向管理器请求一个原型,然后克隆它。这有点像工厂方法模式和原型模式的结合:
#include <map>
#include <string>
#include <memory> // 使用智能指针
// Shape, Circle, Rectangle 定义同上,但 clone() 返回智能指针
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<Shape> clone() const = 0;
virtual void draw() const = 0;
};
// ... Circle, Rectangle 实现 clone() 返回 std::unique_ptr<Shape>
// 例如:
// std::unique_ptr<Shape> Circle::clone() const {
// return std::make_unique<Circle>(*this);
// }
class PrototypeFactory {
public:
static PrototypeFactory& getInstance() {
static PrototypeFactory instance;
return instance;
}
void registerPrototype(const std::string& key, std::unique_ptr<Shape> prototype) {
prototypes[key] = std::move(prototype);
}
std::unique_ptr<Shape> create(const std::string& key) {
if (prototypes.find(key) != prototypes.end()) {
return prototypes[key]->clone();
}
return nullptr;
}
private:
PrototypeFactory() = default;
~PrototypeFactory() = default;
PrototypeFactory(const PrototypeFactory&) = delete;
PrototypeFactory& operator=(const PrototypeFactory&) = delete;
std::map<std::string, std::unique_ptr<Shape>> prototypes;
};
// Client code
int main_with_factory() {
// 1. 初始化原型并注册到工厂
PrototypeFactory::getInstance().registerPrototype("BigRedCircle", std::make_unique<Circle>(20, "Red"));
PrototypeFactory::getInstance().registerPrototype("SmallBlueRect", std::make_unique<Rectangle>(5, 10, "Blue"));
// 2. 客户端通过 key 来创建对象
auto shape1 = PrototypeFactory::getInstance().create("BigRedCircle");
if (shape1) {
shape1->draw();
}
auto shape2 = PrototypeFactory::getInstance().create("SmallBlueRect");
if (shape2) {
shape2->setPosition(200, 200); // 修改克隆体
shape2->draw();
}
// 原型本身不会被修改,可以继续用来创建更多对象
auto anotherCircle = PrototypeFactory::getInstance().create("BigRedCircle");
if(anotherCircle) {
anotherCircle->draw(); // 还是原来的20, "Red"
}
return 0;
}
优点:
-
客户端完全解耦:客户端不知道任何具体类的名称,只需要知道注册的键。
-
动态配置:可以在运行时向管理器添加或删除新的原型,从而改变系统可以创建的对象类型,无需修改代码。
- 使用智能指针管理内存
// 在 Shape 基类中
virtual std::unique_ptr<Shape> clone() const = 0;
// 在 Circle 实现中
std::unique_ptr<Shape> Circle::clone() const override {
return std::make_unique<Circle>(*this);
}
- 处理循环引用
- 当对象图谱中存在循环引用时(A 引用 B,B 又引用 A),进行深拷贝会变得非常棘手,容易导致无限递归和栈溢出。
- 解决方法是,在执行克隆操作时,维护一个map或unordered_map,记录克隆过的对象,当再次遇到之前克隆过的原始对象时,直接从map<Original*, Cloned*>中获取对应的克隆对象。
更多推荐
所有评论(0)