原型模式
例子
发送电子账单
public class Mail implements Cloneable {
/**
* 收信人
*/
private String receiver;
/**
* 邮件名称
*/
private String subject;
/**
* 称谓
*/
private String appellation;
/**
* 邮件内容
*/
private String context;
/**
* 邮件尾部
*/
private String tail;
public Mail(AdvTemplate advTemplate) {
this.context = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
@Override
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
getter...
setter...
}
public class AdvTemplate {
/**
* 信名称
*/
private String advSubject = "xx银行国庆信用卡抽奖活动";
/**
* 信内容
*/
private String advContext = "抽奖活动通知";
public String getAdvSubject(){
return this.advSubject;
}
public String getAdvContext(){
return this.advContext;
}
}
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("版权所有");
while (i < MAX_COUNT) {
// 每封邮件不同的地方
Mail cloneMail = mail.clone();
cloneMail.setAppellation(getRandString(5)+"先生(女士)");
cloneMail.setReceiver(getRandString(5)+"@"+getRandString(8)+".com");
sendMail(cloneMail);
i++;
}
}
/**
* 发送邮件
* @param mail
*/
public static void sendMail(Mail mail) {
System.out.println("标题:" + mail.getSubject() + "\t收件人: " + mail.getReceiver() + "\t...发送成功!");
}
/**
* 获得指定长度的随机字符串
* @param maxLength
* @return
*/
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();
}
}
看Client类中的粗体字mail.clone()这个方法,把对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,如设置称谓、设置收件人地址等。这种不通过new关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
定义
原型模式(Prototype Pattern)的简单程度仅次于单例模式和迭代器模式,使用的场景非常地多。其定义如下:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
原型模式通用源码
public class PrototypeClass implements Cloneable {
@Override
public PrototypeClass clone() {
PrototypeClass prototypeClass = new PrototypeClass();
try {
prototypeClass = (PrototypeClass) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return prototypeClass;
}
}
优点
- 性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一 个循环体内产生大量的对象时,原型模式可以更好地体现其优点。 - 逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。
使用场景
- 资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 - 性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 - 一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的 方法创建一个对象,然后由工厂方法提供给调用者。
注意事项
构造函数不会被执行
一个实现了Cloneable并重写了clone方法的类A,有一个无参构造或有参构造B,通过new关键字产生了一个对象S,再然后通过S.clone()方式产生了一个新的对象T,那么在对象 拷贝时构造函数B是不会被执行的。
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();
}
}
对象拷贝时构造函数确实没有被执行,这点从原理来讲也是可以讲得通的,Object类的 clone方法的原理是从内存中(具体地说就是堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了。
深拷贝和浅拷贝
浅拷贝
public class Thing implements Cloneable {
/**
* 定义一个私有变量
*/
private ArrayList<String> arrayList = new ArrayList<String>();
@Override
public Thing clone() {
Thing thing = null;
try {
thing = (Thing) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return thing;
}
/**
* 取得arrayList的值
* @return
*/
public ArrayList<String> getValue() {
return this.arrayList;
}
/**
* 设置HashMap的值
* @param value
*/
public void setValue(String value) {
this.arrayList.add(value);
}
}
public class Client {
public static void main(String[] args) {
//产生一个对象
Thing thing = new Thing();
//设置一个值
thing.setValue("张三");
//拷贝一个对象
Thing cloneThing = thing.clone();
cloneThing.setValue("李四");
System.out.println(thing.getValue());
}
}
输出结果
[张三, 李四]
怎么会这样呢?怎么会有李四呢?是因为Java做了一个偷懒的拷贝动作,Object类提供的方法clone只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。确实是非常浅,两个对象共享了一个私有变量,你改我改大家都能改,是一种非常不安全的方式,在实际项目中使用还是比较少的(当然,这也是一种“危机”环境的一种救命方式)。你可能会比较奇怪,为什么在Mail那个类中就可 以使用String类型,而不会产生由浅拷贝带来的问题呢?内部的数组和引用对象才不拷贝, 其他的原始类型比如int、long、char等都会被拷贝,但是对于String类型,Java就希望你把它认为是基本类型,它是没有clone方法的,处理机制也比较特殊,通过字符串池(stringpool) 在需要的时候才在内存中创建新的字符串,读者在使用的时候就把String当做基本类使用即可。
注意。使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:一是类的成员变量,而不是方法内变量;二是必须是一个可变的引用对象,而不是一个原始类型或不可变对象。
深拷贝
public class Thing implements Cloneable {
/**
* 定义一个私有变量
*/
private ArrayList<String> arrayList = new ArrayList<String>();
public Thing() {
System.out.println("构造函数被执行了...");
}
@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;
}
/**
* 取得arrayList的值
* @return
*/
public ArrayList<String> getValue() {
return this.arrayList;
}
/**
* 设置HashMap的值
* @param value
*/
public void setValue(String value) {
this.arrayList.add(value);
}
}
在clone方法中对私有的类变量进行独立的拷贝。
输出结果
[张三]
该方法就实现了完全的拷贝,两个对象之间没有任何的瓜葛了,你修改你的,我修改我的,不相互影响,这种拷贝就叫做深拷贝。深拷贝还有一种实现方式就是通过自己写二进制流来操作对象,然后实现对象的深拷贝。
注意。深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,建议的方案是深拷贝和浅拷贝分开实现。
clone和final
对象的clone与对象内的final关键字是有冲突的。
注意。要使用clone方法,类的成员变量上不要增加final关键字。
原型模式先产生出一个包含大量共有信息的类,然后可以拷贝出副本,修正细节信息,建立了一个完整的个性对象。