定义
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要 在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
原型模式可以分为浅克隆和深度克隆
角色
java语言中实现克隆的两种方式
- 直接创建一个对象,然后设置成员变量的值
1
2
3Obj obj=new Obj(); //创建一个新的对象
obj.setName(this.name); //设置其中变量的值
obj.setAge(this.age);
- 实现cloneable接口
浅克隆
如果克隆的对象的成员变量是值类型的,比如int,double那么使用浅克隆就可以实现克隆完整的原型对象,但是如果其中的成员变量有引用类型的,那么这个引用类型的克隆过去的其实是地址,克隆对象的这个引用类型变量改变了,那么原来变量的值也是会改变的。
简单的说,浅克隆只能复制值类型的,对于引用类型的数据只能复制地址
实例
- 一个公司出版周报,那么这个周报的格式一般是相同的,只是将其中的内容稍作修改即可。但是一开始没有这个原型,员工每周都需要重新手写这个周报,现在有了这个周报的原型,只需要在这个clone这个原型,然后在其基础上修改即可。
- 其中的Cloneable就是抽象原型类
- 附件类(这个是一个引用类型的对象,验证浅克隆只是复制其中的地址,如果两个对象中的任何一个改变了这个变量的值,那么另外一个也会随之改变)
1 | /* |
- 周报的类(其中实现了Cloneable接口)
- 其中的clone()方法返回的就是一个克隆的对象,因此我们调用这个方法克隆一个新的对象
1 | /* |
- 测试类
- 测试浅克隆的值类型是是否完成复制了
- 测试引用类型的值能否完成克隆,还是只是复制了一个引用地址
- 从结果来看,对象是完成复制了,因为判断两个对象的地址是不一样的,但是其中的引用类型的成员变量没有完成复制,只是复制了一个地址
1 | public class Client { |
总结
- 浅克隆对于值类型的数据可以复制成功,但是对于引用卡类型的数据只能复制一个地址,如果一个对象中的引用类型的变量的值改变了,那么另外一个也会随之改变
深度克隆
- 浅克隆只能完成复制值类型,深度克隆可以完成复制引用类型和值类型
条件
- 引用类型的变量类实现序列化(实现Serializabl接口)
- 需要克隆的类实现序列化(实现Serializable接口)
为什么实现序列化
- 因为深度克隆的实现的原理是使用输入和输出流,如果想要将一个对象使用输入和输出流克隆,必须序列化。
实现
附件类(引用类型的成员变量,实现序列化)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/*
* 附件类,这个是周报的附件
*/
public class Attachment implements Serializable{
private static final long serialVersionUID = -799959163401886355L;
private String name; // 名称
public Attachment(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}周报类(需要克隆的类,因为其中有引用类型的成员变量,因此需要实现序列化)
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/*
* 这个是周报类,这个类是实现接口Prototype这个接口的
*/
public class WeeklyLog implements Serializable {
private static final long serialVersionUID = -8782492113927035907L;
private String name; // 姓名
private String date; // 日期
private String content; // 内容
private Attachment attachment; // 附件,是一个引用对象,这个只能实现浅克隆
public WeeklyLog() {
super();
}
/**
* 构造方法
*/
public WeeklyLog(String name, String date, String content) {
super();
this.name = name;
this.date = date;
this.content = content;
}
/**
* 提供一个clone方法,返回的是一个clone对象
*/
public WeeklyLog clone() {
// 将对象写入到对象流中
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(
arrayOutputStream); // 创建对象输出流
objectOutputStream.writeObject(this); // 将这个类的对象写入到输出流中
} catch (IOException e) {
e.printStackTrace();
return null;
}
// 将对象从流中读出
ByteArrayInputStream arrayInputStream = new ByteArrayInputStream(
arrayOutputStream.toByteArray());
WeeklyLog weeklyLog;
try {
ObjectInputStream objectInputStream = new ObjectInputStream(
arrayInputStream);// 新建对象输入流
weeklyLog = (WeeklyLog) objectInputStream.readObject(); // 读取对象从流中
return weeklyLog;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}测试类
- 从中可以看出其中的附件地址是不同的,如果一个对象的附件变量改变了,那么另外一个将保持不变,因此实现了深度克隆,是两个完全不同的对象
1 | public class Client { |
总结
- 因为深度克隆使用的是将对象写入输入和输出流中的,因此需要实现序列化,否则将不能完成
总结
浅克隆只能克隆对象中的值类型,不能克隆有引用类型成员变量的对象
使用深度克隆:
- 引用类型的成员变量的类必须实现序列化
- 需要克隆的类必须实现序列化
笔者有话说
- 最近建了一个微信交流群,提供给大家一个交流的平台,扫描下方笔者的微信二维码,备注【交流】,我会把大家拉进群