原型模式概述
原型模式可以通过一个对象实例确定创建对象的种类,并且通过拷贝创建新的实例.总得来说,原型模式实际上就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征.
克隆模式类似于new 但是不同于new,new创建新的对象属性采用的是默认值,克隆出的对象的属性完全与原型对象相同,并且克隆出的新对象改变不会影响原型对象,然后在修改克隆对象的值.
- Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。
- ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
- Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。
原型模式的实例的拷贝包括浅复制和深复制:
- 浅复制:将一个对象复制后,其基本数据类型的变量都会重新创建,而引用类型的变量指向的还是原对象所指向的,也就是指向的内存堆地址没变。
- 深复制:将一个对象复制后,不论是基本数据类型还是引用类型,都是重新创建的。
具体实现
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方法所需要的时间
模拟创建创建对象需要很长时间,对比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的差距还是比较小的.