原型模式可以通过一个对象实例确定创建对象的种类,并且通过拷贝创建新的实例.总得来说,原型模式实际上就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征.
克隆模式类似于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的差距还是比较小的.