原型模式可以通过一个对象实例确定创建对象的种类,并且通过拷贝创建新的实例.总得来说,原型模式实际上就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征.
克隆模式类似于new 但是不同于new,new创建新的对象属性采用的是默认值,克隆出的对象的属性完全与原型对象相同,并且克隆出的新对象改变不会影响原型对象,然后在修改克隆对象的值.
在原型模式结构图中包含如下几个角色:
Object类提供一个clone()方法,可以将一个Java对象复制一份。因此在Java中可以直接使用Object提供的clone()方法来实现对象的克隆,Java语言中的原型模式实现很简单。
需要注意的是能够实现克隆的Java类必须实现一个标识接口Cloneable,表示这个Java类支持被复制。如果一个类没有实现这个接口但是调用了clone()方法,Java编译器将抛出一个CloneNotSupportedException异常。
我们以一封信为例:信中包括的信息有寄信人,寄信时间和收信人,当成功写完信的基本信息的时候,其他人也想写信,此时为了方便就直接以这封信为原型作为模板,然后进行里面局部的修改.
创建一个letter类,实现java中已经提供好的Cloneable接口
java
public class letter implements Cloneable{
private String sender;
private Date sendDate;
private String addressee;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
return obj;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public Date getSendDate() {
return sendDate;
}
public void setSendDate(Date sendDate) {
this.sendDate = sendDate;
}
public String getAddressee() {
return addressee;
}
public void setAddressee(String addressee) {
this.addressee = addressee;
}
public letter() {}
public letter(String sender, Date sendDate, String addressee) {
super();
this.sender = sender;
this.sendDate = sendDate;
this.addressee = addressee;
}
}
客户端
public class ClientLetter {
public static void main(String[] args) throws CloneNotSupportedException {
letter l = new letter("张三",new Date(System.currentTimeMillis()),"李四");
System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
//克隆
letter l1 = (letter) l.clone();
System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
}
}
结果:
张三 Mon Apr 23 19:18:04 CST 2018 李四
张三 Mon Apr 23 19:18:04 CST 2018 李四
从结果可以看出克隆对象和原型对象保持一模一样的内容,并且克隆对象(新对象)可以重新赋值.
上面的例子我们称之为 浅克隆 ,如果我们在代码中修改了时间的值,那么克隆对象的值也会被修改.
public class ClientLetter {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date(System.currentTimeMillis());
letter l = new letter("张三",date,"李四");
//克隆
letter l1 = (letter) l.clone();
//修改时间属性
l.setSendDate(new Date(12345654321L));
System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
}
}
结果:
张三 Mon Apr 23 19:27:35 CST 2018 李四
张三 Sat Feb 14 07:27:34 CST 2009 李四
从结果可以看出当时间修改后克隆对象时间也会修改,因为他们两个时间属性指向的是同一个对象,我们克隆的时候只是把值包括引用地址都一起克隆过来,所以他们引用了同一个对象,此时称之为浅复制.那么有浅就有深,事物都有两面,那么什么是深克隆呢?
深克隆:
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制.
public class letter implements Cloneable{
private String sender;
private Date sendDate;
private String addressee;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
//实现深克隆
letter l = (letter) obj;
l.sendDate = (Date) this.sendDate.clone(); //将属性也克隆
return obj;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public Date getSendDate() {
return sendDate;
}
public void setSendDate(Date sendDate) {
this.sendDate = sendDate;
}
public String getAddressee() {
return addressee;
}
public void setAddressee(String addressee) {
this.addressee = addressee;
}
public letter(String sender, Date sendDate, String addressee) {
super();
this.sender = sender;
this.sendDate = sendDate;
this.addressee = addressee;
}
}
客户端
//深克隆
public class ClientLetter2 {
public static void main(String[] args) throws CloneNotSupportedException {
Date date = new Date(System.currentTimeMillis());
letter l = new letter("张三",date,"李四");
System.out.println(l.getSendDate());
//克隆
letter l1 = (letter) l.clone();
l1.setSender("王五");
//修改时间属性
l.setSendDate(new Date(12345654321L));
System.out.println(l.getSender()+" "+l.getSendDate()+" "+l.getAddressee());
System.out.println(l1.getSender()+" "+l1.getSendDate()+" "+l1.getAddressee());
}
}
从结果可以看出,深克隆技术实现了原型对象和克隆对象的完全独立,对任意克隆对象的修改都不会给其他对象产生影响,是一种更为理想的克隆实现方式。
优点
上面我们说过,克隆模式(原型模式)类似于new ,但是不同于new.并且克隆模式创建对象的效率比使用普通new的对象需要的时间更短.在new对象需要很长时间时可以使用.
缺点
原型模式主要的缺陷就是每个原型必须含有 clone 方法,在已有类的基础上来添加 clone 操作是比较困难的;而且当内部包括一些不支持copy或者循环引用的对象时,实现就更加困难了。
模拟创建创建对象需要很长时间,对比new和clone创建对象需要的时间.
public class Letter implements Cloneable {
public Letter() {
try {
Thread.sleep(10); //模拟创建对象的时间需要很长
}catch (Exception e) {
// TODO: handle exception
}
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
return obj;
}
}
public class test {
public static void main(String[] args) throws CloneNotSupportedException {
test.testNew(1000);
test.testClone(1000);
}
//普通方式创建
public static void testNew(int size) {
long start = System.currentTimeMillis();
for(int i = 0;i<size;i++) {
Letter le = new Letter();
}
long end = System.currentTimeMillis();
System.out.println("new需要的时间"+(end-start));
}
//克隆方式创建
public static void testClone(int size) throws CloneNotSupportedException {
long start = System.currentTimeMillis();
Letter le = new Letter();
for(int i = 0;i<size;i++) {
Letter le1 = (Letter) le.clone();
}
long end = System.currentTimeMillis();
System.out.println("clone需要的时间"+(end-start));
}
}
结果:
new需要的时间10099
clone需要的时间11
可以非常明显的看出,在创建对象需要比较长的时间的时候克隆比new对象需要的时间短的多,但如果创建对象不需要太长时间的时候,new和clone的差距还是比较小的.