原型模式Prototype Pattern
细说原型模式
提示:
博主:章飞 _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:原型模式在拷贝的时候构造函数是不会去执行的。
场景:
当一个类在初始化的时候比较消耗资源的时候,这个时候原型模式是一个好的选择
当在循环体中大量创建一个对象的时候,这个时候原型模式也是一个极佳的选择
注意:
浅拷贝和深拷贝的区别…不要在拷贝的时候,犯错
欢迎继续访问,我的博客