深克隆和浅克隆

在了解原型模式前先了解一下深克隆和浅克隆

浅克隆

浅拷贝是一种对象复制方式,其中只复制对象的成员变量值,而不复制它们所指向的资源。这意味着多
个对象可能会共享相同的资源,包括动态分配的内存、文件句柄等。

深克隆

深拷贝是一种对象复制方式,其中对象的成员变量值被复制,同时资源也被复制,每个对象都有自己独
立的资源副本。这确保了对象之间的数据独立性,不会相互干扰。深拷贝通常需要手动实现拷贝构造函
数和拷贝赋值运算符。

代码验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <iostream>
#include <cstring>

class MyClass {
public:
char* data;

// 默认构造函数,动态分配内存并初始化
MyClass() {
data = new char[10];
for (int i = 0; i < 10; i++) {
data[i] = 'A' + i;
}
}

// 拷贝构造函数
MyClass(const MyClass& other, bool deepCopy) : data(nullptr) {
//如果是深拷贝
if (deepCopy) {
data = new char[10];
std::memcpy(data, other.data, 10);
std::cout << "Deep copy constructor called. Each object will have its own memory.\n";
}
//如果是浅拷贝
else {
data = other.data;
std::cout << "Shallow copy logic used within deep copy constructor (for demonstration purposes only).\n";
}
}
/*
// 析构函数,释放内存
~MyClass() {
delete[] data;
}*/

// 打印数据内容(用于验证数据是否正确)
void print() {
for (int i = 0; i < 10; i++) {
std::cout << data[i];
}
std::cout << std::endl;
}

// 打印指针的地址和数据所指向的内存地址
void printAddresses() {
std::cout << "Address of data member (pointer variable): " << &data << std::endl;
std::cout << "Address pointed to by data: " << static_cast<void*>(data) << std::endl;
}
};

int main() {
MyClass obj1;
obj1.printAddresses();

// 浅拷贝
MyClass obj2(obj1,false);
obj2.printAddresses();

// 验证浅拷贝:两者应该打印相同的数据所指向的内存地址
std::cout << "Comparing addresses pointed to by obj1.data and obj2.data (shallow copy):\n";
std::cout << "obj1.data points to: " << static_cast<void*>(obj1.data) << std::endl;
std::cout << "obj2.data points to: " << static_cast<void*>(obj2.data) << std::endl;

// 深拷贝
MyClass obj3(obj1, true);
obj3.printAddresses();

// 验证深拷贝:两者应该打印不同的数据所指向的内存地址
std::cout << "Comparing addresses pointed to by obj1.data and obj3.data (deep copy):\n";
std::cout << "obj1.data points to: " << static_cast<void*>(obj1.data) << std::endl;
std::cout << "obj3.data points to: " << static_cast<void*>(obj3.data) << std::endl;
return 0;
}

运行结果:

深拷贝浅拷贝.png

如果解除析构函数的注释则会报错,原因是因为浅拷贝obj2所指向的地址在obj1的析构函数里释放掉了

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式之一。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

何时使用

系统应独立于产品的创建、构成和表示。
需要在运行时指定实例化的类,例如通过动态加载。
避免创建与产品类层次平行的工厂类层次。
类的实例只能有几种不同状态组合,克隆原型比手工实例化更方便。

如何解决

通过已有的一个原型对象,快速生成与原型对象相同的实例。

类图示例

原型模式.png

在原型模式结构中定义了一个抽象原型类,所有的Java类都继承自java.lang.Object,而Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以,直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单

举个例子

简历复制

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Resume implements Cloneable {
private String name;
private String sex;
private String age;
private String timeArea;
private String company;

public Resume(String name) {
this.name = name;
}

public void setPersonalInfo(String sex, String age) {
this.sex = sex;
this.age = age;
}

public void setPersonalExperience(String timeArea, String company) {
this.timeArea = timeArea;
this.company = company;
}

public void Display() {
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("sex: " + sex);
System.out.println("timeArea: " + timeArea);
System.out.println("company: " + company);
}

protected Object clone() throws CloneNotSupportedException {
// 调用Object类的clone()方法创建当前对象的浅拷贝
return super.clone();
}
}

public class Prototype {
public static void main(String[] args) throws CloneNotSupportedException {
Resume a = new Resume("zhang3");
a.setPersonalInfo("male", "20");
a.setPersonalExperience("2004-2008", "XX公司");
Resume b = (Resume) a.clone();
b.setPersonalExperience("2004-2006", "YY");
Resume c = (Resume) a.clone();
c.setPersonalInfo("female", "28");
a.Display();
b.Display();
c.Display();
}
}

原型模式包含如下角色

  1. 原型接口(Prototype Interface):定义一个用于克隆自身的接口,通常包括一个 clone() 方法。
  2. 具体原型类(Concrete Prototype):实现原型接口的具体类,负责实际的克隆操作。这个类需要实现 clone() 方法,通常使用浅拷贝或深拷贝来复制自身。
  3. 客户端(Client):使用原型实例来创建新的对象。客户端调用原型对象的 clone() 方法来创建新的对象,而不是直接使用构造函数。

模式分析

优点

  • 性能提高
  • 避免构造函数的约束

缺点

  • 配备克隆方法需要全面考虑类的功能,对已有类可能较难实现,特别是处理不支持串行化的间接对象或含有循环结构的引用时。
  • 必须实现 Cloneable 接口。