昨天主要学习了建造者模式,今天学习的是创建型模式中的最后一个:原型模式。
创建型模式,其实就是用来帮助我们创建对象的。无论是单例模式,工厂模式还是建造者模式,还有今天的原型模式。每一种模式都有自己的战场。原型模式就主要适用于:
new初始化一个实例过程繁琐、访问控制导致创建实例比较耗时的时候,就可以考虑使用原型模式。通过原型模式,可以快速克隆一个全新的实例出来,并且克隆出的对象与原来的对象的值完全一致。
本文主要从以下几个方面来剖析原型模式:
1、原型模式的实现方式及原理
2、原型模式实现过程中的坑
3、原型模式创建实例和new一个实例的效率对比
一、原型模式的实现方式及原理
所谓原型模式,我理解来其实就是对象的克隆。在java中,一切皆是对象,都继承至Object,Object中定义了native修饰的本地方法clone(),用于对象的克隆。因为是本地方法,所以效率会特别高。
实现过程以People类为例,People类实现Cloneable接口。在People类中重写clone()方法。代码如下:
package com.zwh.gof23.prototype02;
import java.util.Date;
/**
* 提供克隆方法的People对象,仅仅是测试代码,别真的去克隆人。违背伦理
* @author zwh
*
*/
public class People implements Cloneable{
private String name;
private Date birthday;
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj=super.clone();
return obj;
}
public People(String name, Date birthday) {
super();
this.name = name;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
这边简单的实现了一个原型模式,用一个测试方法进行测试。
package com.zwh.gof23.prototype02;
import java.util.Date;
public class TestClient {
/**
* @param args
* @throws CloneNotSupportedException
*/
public static void main(String[] args) throws CloneNotSupportedException {
Date date=new Date(1243289148726194721L);
People people=new People("张三", date);
displayPeople(people);
People people2=(People) people.clone();
displayPeople(people2);
}
static void displayPeople(People people){
System.out.println("===========people实例============");
System.out.println("people:"+people);
System.out.println("name:"+people.getName());
System.out.println("birthday:"+people.getBirthday());
}
}
输出结果如下:
===========people实例============
people:com.zwh.gof23.prototype02.People@6d06d69c
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
===========people实例============
people:com.zwh.gof23.prototype02.People@33909752
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
可以发现,已经实现了对象的克隆。并且复制了对象属性的值。
二、原型模式实现过程中的坑
虽然这里说是原型模式实现过程中的坑,但其实这并不是原型模式中的坑,而是java面向对象的特征。
在上面的例子中,birthday属性的值引入的是一个Date对象,当调用clone()方法进行克隆时,People对象中的子对象Date并没有进行克隆,我们称这种复制方式为浅复制。显然,这并不太符合我们的克隆。测试一下,在克隆完成之后,修改date的内容。
修改后的测试类中main方法:
Date date=new Date(1243289148726194721L);
People people=new People("张三", date);
displayPeople(people);
People people2=(People) people.clone();
date.setTime(1239821047219857918L);
displayPeople(people2);
看看输出结果:
===========people实例============
people:com.zwh.gof23.prototype02.People@6d06d69c
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
===========people实例============
people:com.zwh.gof23.prototype02.People@33909752
name:张三
birthday:Tue Dec 18 10:50:57 CST 39290334
很明显,两次birthday的内容是不一样的。说明两者共同指向了同一个date对象。要避免这种情况,可以有两种解决方式。也就是深复制。
实现深复制的两种方式分别是:clone()方法中对子对象进行克隆、或者使用
序列化及反序列化进行克隆。
首先说第一种,子对象克隆:修该People类中的clone方法如下:
protected Object clone() throws CloneNotSupportedException {
Object obj=super.clone();
People people = (People) obj;
people.birthday=(Date) this.birthday.clone();
return obj;
}
在调用clone方法时,对对象的birthday属性也进行一次clone,达到深复制的目的。沿用上面的测试方法,输出结果如下:
===========people实例============
people:com.zwh.gof23.prototype02.People@6d06d69c
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
===========people实例============
people:com.zwh.gof23.prototype02.People@33909752
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
由此可见,date对象的值的改变,并没有影响到已经克隆出来的people2对象。但毫无疑问,people对象的birthday属性的值已经改变了。可以自行进行打印测试。
第二种实现深复制的克隆方式是使用序列化及反序列化方法,实现代码如下:
1、修改People类,实现Serializable'接口,新增使用序列化及反序列化克隆的方法:
/**
* 使用序列化及反序列化克隆
* @param people
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
People clonePeopleBySerialize() throws IOException, ClassNotFoundException{
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bos);
oos.writeObject(this);
byte[] bytes=bos.toByteArray();
ByteArrayInputStream bis=new ByteArrayInputStream(bytes);
ObjectInputStream ois=new ObjectInputStream(bis);
People people2=(People) ois.readObject();
return people2;
}
测试代码如下:
Date date=new Date(1243289148726194721L);
People people=new People("张三", date);
displayPeople(people);
People people2=people.clonePeopleBySerialize();
date.setTime(1239821047219857918L);
displayPeople(people2);
运行结果如下:
===========people实例============
people:com.zwh.gof23.prototype02.People@6d06d69c
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
===========people实例============
people:com.zwh.gof23.prototype02.People@214c265e
name:张三
birthday:Mon Sep 22 04:23:14 CST 39400234
可见也实现了深复制,date值得改变没有影响到已经克隆好的people2对象。
三、原型模式创建对象与new方式创建对象的效率对比
首先,为People类创建空构造器,构造是进行休眠的方式来模拟使用new关键字创建实例耗时的情况。模拟每次创建一个对象都需要10毫秒。
public People(){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
模拟创建1000个对象实例,测试代码如下:
package com.zwh.gof23.prototype02;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;
import com.zwh.gof23.prototype.Sheep;
public class TestClient {
/**
* @param args
* @throws CloneNotSupportedException
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
int count=1000;
testCreatObjectByNew(count);
testCreatObjectByClone(count);
testCreatObjectBySerializeable(count);
/*Date date=new Date(1243289148726194721L);
People people=new People("张三", date);
displayPeople(people);
People people2=people.clonePeopleBySerialize();
date.setTime(1239821047219857918L);
displayPeople(people2);*/
/*People people2=(People) people.clone();
date.setTime(1239821047219857918L);
System.out.println(people.getBirthday());
displayPeople(people2);*/
}
/**
* 测试使用new关键字创建对象耗时
* @param count
*/
static void testCreatObjectByNew(int count){
People people=new People();
long start = System.currentTimeMillis();
for (int i = 0; i <count; i++) {
People people2=new People();
}
long end=System.currentTimeMillis();
System.out.println("使用new方式创建对象,时间为:"+(end-start));
}
/**
* 测试使用clone方式创建对象耗时
* @param count
* @throws CloneNotSupportedException
*/
static void testCreatObjectByClone(int count) throws CloneNotSupportedException{
People people=new People();
long start = System.currentTimeMillis();
for (int i = 0; i <count; i++) {
People people2=(People) people.clone();
}
long end=System.currentTimeMillis();
System.out.println("使用clone方式创建对象,时间为:"+(end-start));
}
/**
* 测试使用序列化及反序列化方式创建对象耗时
* @param count
* @throws ClassNotFoundException
* @throws IOException
*/
static void testCreatObjectBySerializeable(int count) throws ClassNotFoundException, IOException{
People people=new People();
long start = System.currentTimeMillis();
for (int i = 0; i <count; i++) {
People people2=people.clonePeopleBySerialize();
}
long end=System.currentTimeMillis();
System.out.println("使用序列化及反序列化方式创建对象,时间为:"+(end-start));
}
/**
* 打印对象信息
* @param people
*/
static void displayPeople(People people){
System.out.println("===========people实例============");
System.out.println("people:"+people);
System.out.println("name:"+people.getName());
System.out.println("birthday:"+people.getBirthday());
}
}
测试结果:
使用new方式创建对象,时间为:10184
使用clone方式创建对象,时间为:1
使用序列化及反序列化方式创建对象,时间为:368
由此可见,当在使用new来创建对象实例比较复杂,耗时的情况下,使用克隆来实现原型模式,能极大提高效率。