java学习笔记-设计模式之原型模式

昨天主要学习了建造者模式,今天学习的是创建型模式中的最后一个:原型模式。

创建型模式,其实就是用来帮助我们创建对象的。无论是单例模式,工厂模式还是建造者模式,还有今天的原型模式。每一种模式都有自己的战场。原型模式就主要适用于:

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来创建对象实例比较复杂,耗时的情况下,使用克隆来实现原型模式,能极大提高效率。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云间歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值