模式定义:
使用原型实例指定创建对象的种类,并且通过克隆这些原型创建新的对象。原型模式是一种对象创建型模式。
使用场景:
1、在你希望创建一个对象的时候,不仅仅想要克隆原型实例对象的基础结构,还需要他当前的对象数据。
2、对于克隆的对象的操作并不影响原型实例对象。
以克隆层次来区分,可有深克隆和浅克隆两种。
浅克隆实现如下:
public class PrototypeInstance implements Cloneable{
private Integer id;
private String name;
private String content;
private Date createDate;
private ArrayList<Integer> titles = new ArrayList<>();
public void addTitles(Integer value) {
this.titles.add(value);
}
// 重写clone方法,clone来源于Object类
@Override
protected PrototypeInstance clone() {
try {
PrototypeInstance entity = (PrototypeInstance) super.clone();
entity.setName(getName());
entity.setContent(getContent());
return entity;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
// get / set method 自行补齐
}
@Test
public void CloneInstanceTest() {
String content = new String("当一个种花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
PrototypeInstance cloneInstance = instance.clone();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
运行结果:
浅克隆就是拷贝一份原型实例对象的所有字段,并把字段中的引用也拷贝了。
那么它会暴露什么问题尼?
程序总要修改对象的属性值的吧,那我们就来试试吧,如下:
public class CloneTest {
@Test
public void CloneInstanceTest() {
String content = new String("当一个种花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
PrototypeInstance cloneInstance = instance.clone();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
// 克隆完后,我突然想修改原型实例对象的值。
System.out.println("---------- 尝试修改原型实例对象值 -----------");
id = 2;
content = new String("一颗中国心");
date.setTime(1365215478956L);
instance.setId(id);
instance.addTitles(3);
instance.setContent(content);
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
}
运行的结果:
从3 4 中看出来,怎么克隆对象的属性值也变了?1 2 且没有变化。
是否可以推断一下:
Integer / String 等字段引用类型,在原型实例对象修改字段值后克隆对象并没有产生变化,但是 Date / ArrayList 类型的字段在原型实例对象修改完值后克隆对象中它的字段值却变了。
深入源码了解下的话,你会发现 Date / ArrayList 类型
它们都是实现了Cloneable接口以及使用了transient关键字
transient关键字:java语言的关键字,变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。所以在进行对象克隆的时候,Date / ArrayList 类型并没有在序列化中进行引用拷贝,仅仅是拷贝存储值,然而clone的时候 它们会进行值的拷贝。
结论: 因为浅度克隆对引用类型的字段进行的是拷贝了指向对象的引用,而基本类型是对值进行备份。
那么如何解决这个问题尼?
也就是我们接下来要说的深克隆了。
解决方式1: 就如刚才看到的源码一样 它们两个是实现了Cloneable接口的,所以肯定是有重写clone()方法啦。源码里是这样的:
所以修改原性实例对象 clone方法里面的拷贝字段就可以做到了:
运行的结果:
完全正确,原性对象 和 克隆对象两者互不干预,不相互影响,你修改你的,我写我的
解决方式2: 直接使用序列化和反序列化来拷贝呗,代码如下:
public class PrototypeInstance implements Cloneable,Serializable{
private Integer id;
....
public class CloneTest {
@Test
public void CloneInstanceTest() throws IOException, ClassNotFoundException {
String content = new String("当一个种花家");
Integer id = 1;
Date date = new Date(1565515734562L);
PrototypeInstance instance = new PrototypeInstance();
instance.setId(id);
instance.setName("花花");
instance.setContent(content);
instance.setCreateDate(date);
instance.addTitles(1);
instance.addTitles(2);
// PrototypeInstance cloneInstance = instance.clone();
// 利用序列化和反序列化进行克隆
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(instance);
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
PrototypeInstance cloneInstance = (PrototypeInstance) ois.readObject();
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
// 克隆完后,我突然想修改原型实例对象的值。
System.out.println("---------- 尝试修改原型实例对象值 -----------");
id = 2;
content = new String("一颗中国心");
date.setTime(1365215478956L);
instance.setId(id);
instance.addTitles(3);
instance.setContent(content);
System.out.println("克隆" + instance.toString());
System.out.println("克隆" + cloneInstance.toString());
}
}
优点方面
原型模式是在内存二进制流的拷贝,创建对象时并不会调用类的构造方法,所以要比直接new一个对象性能好很多,如果是大量的new 对象的话 这种性能显现的特别明显。