1 定义:
1.1 定义:Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象)。
1.2 通用类图:
原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示这个对象是可拷贝的。“标示”:在JDK的帮助中,Cloneable是一个方法都没有的,这个接口只是一个标记作用,在JVM中具有这个标记的对象才有可能被拷贝。
从可能到可以,还需要覆盖从Object继承下来的clone( )方法。
1.3 通用代码:
public class PrototypeClass implements Cloneable {
// 覆写父类Object方法
@Override
public PrototypeClass clone() {
PrototypeClass prototypeClass = null;
try {
prototypeClass = (PrototypeClass) super.clone();
} catch (CloneNotSupportedException e) {
// 异常处理
}
return prototypeClass;
}
}
2 优点
2.1 性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2.2 逃避构造函数的约束:也是缺点,直接在内存中拷贝,构造函数是不会执行的。
3 缺点
如上2.2.
4 应用场景
4.1 资源优化场景:类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等;
4.2 性能和安全要求的场景:通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以用原型模式。(即回避new的调用)
4.3 一个对象多个修改者的场景:一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。(这个有点不敢苟同:改写的结果如何汇总?莫非是其他对象只修改此对象的某一特定值域或成员?)
原型模式很少单独出现,一般是工厂方法模式(可以创建对象,供其复制嘛)一起出现,通过clone的方法创建另一个对象,然后由工厂方法提供给调用者。
5 注意事项
5.1 构造函数不会被执行:
Object类的clone方法的原理是从内存中(具体地说是堆内存)以二进制的方式进行拷贝,重新分配一个内存块,那构造函数没有执行也是非常正常的了。
源代码示例:
public class Thing implements Cloneable {
public Thing() {
System.out.println("构造函数被执行了...");
}
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}
public class Client {
public static void main(String[] args) {
// 产生一个对象
Thing thing = new Thing();
// 拷贝一个对象
Thing cloneThing = thing.clone();
}
}
构造函数在clone()时,没有被调用。
5.2 浅拷贝和深拷贝:
Java做了一个偷懒的拷贝动作:Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。
那么String类型 呢?
内部的数组和引用对象才不拷贝,其他的原始类型比如int、long、char等都会拷贝,但是对于String类型,Java就希望你把它认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串池(stringpool)在需要时才在内在中创建新的字符串,读者在使用的时候把String当基本类使用即可。
注意一:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内的变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。
注意二:深拷贝与浅拷贝建议不要混合用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现。
如何进行深拷贝呢?对类变量(待深拷贝类的成员)进行独立的拷贝。
源代码:浅拷贝与深拷贝
public class Thing implements Cloneable {
// 定义一个私有变量
private final ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing) super.clone(); // 仅此一句为 浅拷贝
// this.arrayList =
// (ArrayList<String>)this.arrayList.clone();//加此句 变为深拷贝
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
// 设置HashMap的值
public void setValue(String value) {
this.arrayList.add(value);
}
// 取得arrayList的值
public ArrayList<String> getValue() {
return this.arrayList;
}
}
// 测试:
public class Client {
public static void main(String[] args) {
// 产生一个对象
Thing thing = new Thing();
// 设置一个值
thing.setValue("张三");
thing.setValue("李四");
// 拷贝一个对象
Thing cloneThing = thing.clone();
cloneThing.setValue("李四");
System.out.println(thing.getValue());
}
}
浅拷贝会有“张三 李四”, 深拷贝只有“张三”。
5.3 clone与final关键字的冲突:(要使用clone()方法,类的成员变量上就不要增加final关键字)。
类内部成员若定义为final型,则意味着,定义时便一次性的赋值了。那么通过clone( )方法返回的值又给谁呢?
如下代码,会在标注处:编译器报斜体部分错误。
public class Thing implements Cloneable {
// 定义一个私有变量
private final ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing) super.clone();
thing.arrayList = (ArrayList<String>) this.arrayList.clone();// //
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
}
6 扩展
暂无
7 范例
(批量发送邮件实例)
类图如下:
源代码如下:
package _07_Prototype;
/**
* @author cbf4Life cbf4life@126.com I'm glad to share my knowledge with you
* all. 需要发送出去的邮件账单
*/
public class Mail implements Cloneable {
// 收件人
private String receiver;
// 邮件名称
private String subject;
// 称谓
private String appellation;
// 邮件内容
private String contxt;
// 邮件的尾部,一般都是加上“XXX版权所有”等信息
private String tail;
// 构造函数
public Mail(AdvTemplate advTemplate) {
this.contxt = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
@Override
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return mail;
}
// 以下为getter/setter方法
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 getContxt() {
return contxt;
}
public void setContxt(String contxt) {
this.contxt = contxt;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
}
public class AdvTemplate {
// 广告信名称
private String advSubject = "XX银行国庆信用卡抽奖活动";
// 广告信内容
private String advContext = "国庆抽奖活动通知:只要刷卡就送你1百万!....";
// 取得广告信的名称
public String getAdvSubject() {
return this.advSubject;
}
// 取得广告信的内容
public String getAdvContext() {
return this.advContext;
}
}
public class Client {
// 发送账单的数量,这个值是从数据库中获得
private static int MAX_COUNT = 10000000;
// 每次查看一种方式。以进行比较。
public static void main(String[] args) {
long begin = System.currentTimeMillis();
usePrototype();
long end = System.currentTimeMillis();
System.out.println("使用原型模式发送1000000封邮件历时:" + (end - begin) / 1000
+ "s");
// long begin = System.currentTimeMillis();
// useNew();
// long end = System.currentTimeMillis();
// System.out.println("使用new发送1000000封邮件历时:" + (end - begin) / 1000
// + "s");
}
public static void usePrototype() {
// 模拟发送邮件
int i = 0;
// 把模板定义出来,这个是从数据中获得
Mail mail = new Mail(new AdvTemplate());
mail.setTail("XX银行版权所有");
while (i < MAX_COUNT) {
// 以下是每封邮件不同的地方
Mail cloneMail = mail.clone();
cloneMail.setAppellation(getRandString(5) + " 先生(女士)");
cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8)
+ ".com");
// 然后发送邮件
sendMail(cloneMail);
i++;
}
}
public static void useNew() {
// 模拟发送邮件
int i = 0;
while (i < MAX_COUNT) {
// 把模板定义出来,这个是从数据中获得
Mail mail = new Mail(new AdvTemplate());
mail.setTail("XX银行版权所有");
mail.setAppellation(getRandString(5) + " 先生(女士)");
mail.setReceiver(getRandString(5) + "@" + getRandString(8)
+ ".com");
// 然后发送邮件
sendMail(mail);
i++;
}
}
// 发送邮件
public static void sendMail(Mail mail) {
// System.out.println("标题:"+mail.getSubject() +
// "\t收件人:"+mail.getReceiver()+"\t....发送成功!");
}
// 获得指定长度的随机字符串
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();
}
}
实测结果:
使用原型模式发送1000000封邮件历时:31s
使用new发送1000000封邮件历时:29s
可能的原因:所需要复制的单项数据太小,体现不出差距。