原型设计模式

原型设计模式就是在运行过程中,通过拷贝创建新的对象,而不是通过实例化一个类,这样可以减少对象的创建成本,适用于以下场景

  1. 对象创建的成本较高,例如需要大量的计算和网络IO
  2. 需要创建大量相似的对象
  3. 需要动态的创建对象并且希望避免传统的创建方式

在这些场景,原型设计模式可以显著提升系统性能和可拓展性,因为他可以避免频繁的对象创建和销毁

实现方式

浅拷贝

浅拷贝是指在复制对象时,只复制对象的基本数据类型的值和引用类型的地址,不复制引用类型指向的对象本身

img

浅拷贝的实现可以适用直接赋值或clone方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ShallowClone implements Cloneable{
private Student student;

public void setStudent(Student student) {
this.student = student;
}

public Student getStudent() {
return student;
}

@Override
public String toString() {
return "ShallowClone{" +
"student=" + student +
'}';
}

@Override
protected ShallowClone clone() throws CloneNotSupportedException {
return (ShallowClone) super.clone();
}
}

深拷贝

深拷贝于浅拷贝最直接的不同就是引用对象,浅拷贝出来的对象和原型指向同一个对象,但是深拷贝会创建出新的指向对象

深拷贝的实现也有两种方式:递归克隆和序列化反序列化

递归克隆

递归克隆就是在复制引用对象的时候创建一个新的对象,并对他的属性继续适用clone方法

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
class Product implements Cloneable {
private String name;
private double price;
private int stock;
// 省略构造函数、getter和setter方法
@Override
public Product clone() {
try {
return (Product) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销规则
class PromotionRule implements Cloneable {
private String type;
private double discount;
private Product product;
// 省略构造函数、getter和setter方法
@Override
protected PromotionRule clone() {
try {
PromotionRule promotionRule = (PromotionRule) super.clone()
Product product = (Product)product.clone();
promotionRule.setProduct(product);
return promotionRule;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
// 促销活动
class PromotionEvent implements Cloneable {
private String name;
private Date startDate;
private Date endDate;
private List rules;
// 省略构造函数、getter和setter方法
// 在促销活动中的clone方法需要克隆里边所有的非基础数据类型
@Override
protected PromotionEvent clone() {
try {
PromotionEvent clonedEvent = (PromotionEvent) super.clone();
clonedEvent.startDate = (Date) startDate.clone();
clonedEvent.endDate = (Date) endDate.clone();
clonedEvent.rules = new ArrayList<>();
for (PromotionRule rule : rules) {
clonedEvent.rules.add(rule.clone());
}
return clonedEvent;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}

序列化

深拷贝的第二种实现方式就是序列化,将对象原型序列化,在对序列化的二进制流反序列化得到相同的对象,例如先转JSON再反序列化为对象,破坏单例设计模式中也提到过序列化

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws IOException, ClassNotFoundException {
EagerSingleton singleton = EagerSingleton.getInstance();
FileOutputStream fout = new FileOutputStream("D://singleton.txt");
ObjectOutputStream out = new ObjectOutputStream(fout);
out.writeObject(singleton);
// 将实例反序列化出来
FileInputStream fin = new FileInputStream("D://singleton.txt");
ObjectInputStream in = new ObjectInputStream(fin);
Object o = in.readObject();
System.out.println(o==singleton);
}

对于深拷贝和浅拷贝的选择要根据场景,如果拷贝对象没有引用对象或者不需要对引用对象进行修改可以使用浅拷贝,如果含有引用对象并且要对引用对象进行修改可以使用深拷贝

应用场景

假设有一个系统A,要从系统B中更新数据,数据量很大,而且我们希望在更新的过程中不会出现一部分数据是更新之后的值,一部分数据是更新前的值,并且更新过程要保证系统的可用。

这个时候,我们可以使用深拷贝,将系统A的数据分为表A和表B,表A对外提供查询,在数据更新时

  1. 表A先深拷贝将数据给表B
  2. 系统B将数据同步给表B
  3. 当表B数据加载后,用原子操作将表A和表B互换
  4. 删除表B,为下一次数据更新做准备

这个方法其实跟jdk中的CopyOnWriteArrayList类似,在实际运用中,在这种读多写少并且要保证原子性和可用性的场景下,可以使用深拷贝来解决