淘先锋技术网

首页 1 2 3 4 5 6 7

细说原型模式

提示:
博主:章飞 _906285288的博客
博客地址:
http://blog.csdn.net/qq_29924041


细说原型模式

原型模式这个模式的简单程度是仅次于单例模式和迭代器模式,非常简单,但是要使
用好这个模式还有很多注意事项。原型,顾名思义,也就是本来的模型只有一个,类似电子工业生产中的模具,有了这个模具之后,后面所有的产品都可以由这个模具生产出来,但是这个模具生产出来后,可以完全一样,也可以在生产的产品后再修改出一部分差异化的东西。这就是原型模式。

定义

原型模式:用原型实例指定创建对象的种类,通过拷贝这些原型来创建新的对象。
其实也就是使用java的拷贝原理,在原型的基础之上创建新的对象而已。

UML模型

在这里插入图片描述
从上述的UML图中就可以看到,原型模式是非常简单的,原型类中只有clone一个方法,而在java中提供了Cloneable这个接口来标识对象的可拷贝特性。这个接口其实只是一个标记作用,在JVM中通过这个标记来识别出这个对象具有可拷贝的特性。

基于UML的代码

package src.com.zzf.designpattern.prototypepattern.demo4;

/**
 * 原型对象,实现Cloneable接口,重写clone方法
 * @author zhouzhangfei
 *
 */
public class PrototypeClass implements Cloneable{
	public String name = "123";
	
	//重写clone方法
	@Override
	protected PrototypeClass clone()  {
		// TODO Auto-generated method stub
		PrototypeClass prototypeClass = null;
		try {
			prototypeClass =  (PrototypeClass) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return prototypeClass;
	}
}


    package src.com.zzf.designpattern.prototypepattern.demo4;

/**
 * 测试代码,通过拷贝方法,创建出了两个不同的对象
 * @author zhouzhangfei
 *
 */
public class Test {
	public static void main(String[] args) {
		//原型模式创建原始对象
		PrototypeClass prototypeClass = new PrototypeClass();
		System.out.println(prototypeClass.toString()+"\t"+prototypeClass.name);
		//通过克隆拷贝方法,创建出一个新的对象
		PrototypeClass prototypeClass2 = prototypeClass.clone();
		System.out.println(prototypeClass2.toString()+"\t"+prototypeClass2.name);
		
		//从上面可以看出,基本数据类型的拷贝是随着对象一起被克隆出去的,即两个对象的基本数据类型值是一致的
	}
}


场景

在之前所有关于设计模式的博客中都讲到了场景,同样,原型模式在实际的应用过程中也有非常多的场景。

场景一

设计模式之禅中举了一个群发邮件的案例,定义一个邮件的模板类,通过改变邮件的收件者和内容,然后修改邮件的发送。这个在实际开发过程中是有很广泛的应用的。如果通过new的方式,会造成大量的性能浪费,而使用原型模式,通过内存已有的对象,把对象拷贝一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据。有助于提高性能

代码

代码一

package src.com.zzf.designpattern.prototypepattern.demo1;


/**
 * 广告信的模板
 * @author Administrator
 *
 */
public class AdvTemplate {
	private String advSubject = "XX银行信用卡抽奖活动";
	private String advContext = "国庆抽奖活动:只要刷卡就送你一百万!...";
	public String getAdvSubject() {
		return advSubject;
	}
	public void setAdvSubject(String advSubject) {
		this.advSubject = advSubject;
	}
	public String getAdvContext() {
		return advContext;
	}
	public void setAdvContext(String advContext) {
		this.advContext = advContext;
	}
	
}
package src.com.zzf.designpattern.prototypepattern.demo1;


public class Mail implements Cloneable{
	//收件人
	String receiver;
	//邮件名称
	String subject;
	//称谓
	String appellation;
	//邮件内容
	String context;
	//邮件的尾部
	String tail;
	
	public Mail(AdvTemplate mAdvTemplate) {
		this.context = mAdvTemplate.getAdvContext();
		this.subject = mAdvTemplate.getAdvSubject();
	}
	public String getReceiver() {
		return receiver;
	}
	public void setReceiver(String receiver) {
		this.receiver = receiver;
	}
	public String getSubject() {
		return subject;
	}
	public void setSubject(String subject) {
		this.subject = subject;
	}
	public String getAppellation() {
		return appellation;
	}
	public void setAppellation(String appellation) {
		this.appellation = appellation;
	}
	public String getContext() {
		return context;
	}
	public void setContext(String context) {
		this.context = context;
	}
	public String getTail() {
		return tail;
	}
	public void setTail(String tail) {
		this.tail = tail;
	}
	
	@Override
	protected Mail clone() {
		// TODO Auto-generated method stub
		Mail mail = null;
		try {
			mail = (Mail) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return mail;
	}
}

package src.com.zzf.designpattern.prototypepattern.demo1;


import java.util.Random;
/**
 * 一是类初始化需要消化非常多的资源,这个资源包括数据、硬件资源
等;二是通过new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式;
三是一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对
象供调用者使用。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone
的方法创建一个对象,然后由工厂方法提供给调用者。
 * @author Administrator
 *
 */
public class Client {
	// 发送账单的数量,这个值是从数据库中获得
	private static int MAX_COUNT = 6;

	public static void main(String[] args) {
		// 模拟发送邮件
		int i = 0;
		// 把模板定义出来,这个是从数据库中获得
		Mail mail = new Mail(new AdvTemplate());
		mail.setTail("XX银行版权所有");

		while (i < MAX_COUNT) {
			// 以下是每封邮件不同的地方
			Mail mailclone = mail.clone();
			mailclone.setAppellation(getRandString(5) + " 先生(女士)");
			mailclone.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
			// 然后发送邮件
			sendMail(mailclone);
			i++;
		}
	}

	// 获得指定长度的随机字符串
	public static String getRandString(int maxLength) {
		String source = "abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		StringBuffer sb = new StringBuffer();
		Random rand = new Random();
		for (int i = 0; i < maxLength; i++) {
			sb.append(source.charAt(rand.nextInt(source.length())));
		}
		return sb.toString();
	}

	// 发送邮件
	public static void sendMail(Mail mail) {
		System.out.println("标题:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t....发送成功!");
	}
	
}

注意:
对象拷贝时,类的构造函数是不会被执行的

基于原型模式的深拷贝与浅拷贝

既然原型模式是通过拷贝的形式来进行的,那也就有必要分析一下什么叫做浅拷贝,什么叫深拷贝

浅拷贝

浅拷贝(Shallow Copy):①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。②**对于数据类型是引用数据类型的成员变量,**比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
如下描述代码所示:

package src.com.zzf.designpattern.prototypepattern.demo2;


/**
 * 浅拷贝对象,
 * @author zhouzhangfei
 *
 */
public class Thing implements Cloneable{
	//基本数据类型,int,String,long会直接拷贝一份过去
	public String nameString ="12345";
	public String string = new String("9999999");
	//数组对象不会直接拷贝,而是会将其地址拷贝一份过去,导致两个对象会访问同一块内存区域
	public int [] array = new int[]{};
	public  Thing() {
		System.out.println("构造函数被执行了");
	}
	
	@Override
	protected Thing clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Thing mThing = null;
		mThing = (Thing) super.clone();
		return mThing;
	}
	
	
}

package src.com.zzf.designpattern.prototypepattern.demo2;


/**
 * 浅拷贝
 * @author Administrator
 * 内部的数组和引用对象不拷贝,其他的原始类型比如int,long,String(Java 就希望你把String 认为是基本类型,String 是没有clone 方法的)等都会被拷贝的。
 */
public class Test {
	public static void main(String[] args) {
		Thing mThing = new Thing();
		System.err.println(mThing.nameString);
		System.out.println(mThing.array);
		try {
			Thing cloneThing = mThing.clone();
			cloneThing.nameString = "445567";
			System.out.println(cloneThing.nameString);
			System.out.println(cloneThing.string);
			System.out.println(cloneThing.array);
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

运行结果为:
构造函数被执行了
12345
[I@7852e922
445567
9999999
[I@7852e922

从结果中就可以看到。对于String,new String等数据类型结构,是直接在新的对象中拷贝了一份,而对于数组对象来说,则是把原型对象中的数组的地址拷贝了一份给新的对象,这就会导致两个数组对象会同时去访问同一块内存地址。这就是导致一种叫做脏数据的现象

深拷贝

深拷贝(Deep Copy):对原型对象的所有数据结构都进行了拷贝。也就是对原型对象中的引用数据类型,集合,数组,等都在新的拷贝对象中开辟了一个新的空间。
如下案例所示:

package src.com.zzf.designpattern.prototypepattern.demo3;


import java.util.ArrayList;
/**
 * Clone 与final 两对冤家
 * 删除掉final 关键字,这是最便捷最安全最快速的方式,你要使用clone 方法就在类
的成员变量上不要增加final 关键字
 * @author Administrator
 *
 */
public class Thing implements Cloneable{
	
	private ArrayList<String> arrayList = new ArrayList<String>();
	public int [] array = new int[]{};
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		Thing thing = null;
		thing = (Thing) super.clone();
		thing.arrayList = (ArrayList<String>) this.arrayList.clone();
		this.array = this.array.clone();
		return thing;
	}
	
	public void setValue(String obj) {
		this.arrayList.add(obj);
	}
	
	public ArrayList<String> getValue() {
		return arrayList;
	}
}

package src.com.zzf.designpattern.prototypepattern.demo3;


/**
 * 
 * @author Administrator
 *
 */
public class Client {
	
	public static void main(String[] args) {
		//产生一个对象
		Thing thing = new Thing();
		//设置一个值
		thing.setValue("张三");
		
		//拷贝一个对象
		Thing cloneThing = null;
		try {
			cloneThing = (Thing) thing.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		cloneThing.setValue("李四");
		System.out.println(thing.array);
		System.out.println(thing.getValue());
		System.out.println(cloneThing.getValue());
		System.out.println(cloneThing.array);
	}
	
}

执行结果为:
[I@7852e922
[张三]
[张三, 李四]
[I@4e25154f

从上面结果中可以看到。数组对象地址发生了改变,也就是数组对象是重新开辟了一块内存区域,而集合对象也发生了改变,拷贝对象中包含了原型对象中的元素,但是拷贝对象在修改完自己内部的集合的时候,并不会引起原型对象集合内容的改变,这就是深度拷贝,拷贝的不仅仅是基本数据类型,会将引用数据类型的地址在新的对象中重新开辟一份出来

原型模式clone与final冤家路窄

final关键字我们都知道是不能改变的对吧,而clone是为了拷贝而存在的,clone对象的属性是随时都可以发生改变的,这也就造成了鱼与熊掌不可兼得的问题。
解决方式:
删除掉final 关键字,这是最便捷最安全最快速的方式,你要使用clone 方法就在类
的成员变量上不要增加final 关键字

原型模式应用和注意事项

首先说优点吧:
1:原型模式是在内存的二进制流中进行的拷贝动作,。所以它会比new对象来的更快一些,也就是性能上会优化很多,尤其是在一个循环体内部产生大量对象的时候,这个时候非常推荐使用原型模式
2:原型模式在拷贝的时候构造函数是不会去执行的。

场景:
当一个类在初始化的时候比较消耗资源的时候,这个时候原型模式是一个好的选择
当在循环体中大量创建一个对象的时候,这个时候原型模式也是一个极佳的选择

注意:
浅拷贝和深拷贝的区别…不要在拷贝的时候,犯错




欢迎继续访问,我的博客