一、原型模式介绍:
原型模式的核心就一个clone方法。试想如下的场景,一个对象需要被多个修改者修改或者某个项目需要大量的对象但每个对象仅修改一些细节,比如,银行给不同客户发送的具有尊称的定制化信息或者邮件。不通过new关键字来产生一个对象, 而是通过对象复制来实现的模式就叫做原型模式。原型模式理解上比较简单,但是难点在于正确理解java中的clone方法,因为clone方法的正确使用涉及到浅拷贝和深拷贝的问题。
二、代码示例:
假设银行需要给不同的客户发送具有尊称的定制化广告信息。
首先是确认广告模板:
/** 广告信模板 */
class AdvTemplate {
private String advSubject ="XX银行国庆信用卡抽奖活动";
private String advContext = "国庆抽奖活动通知: 只要刷卡就送你一百万! ...";
public String getAdvSubject(){
return this.advSubject;
}
public String getAdvContext(){
return this.advContext;
}
}
然后是邮件内容:
/** 邮件实现类:邮件定制化内容 + 通用内容(广告) */
class Mail implements Cloneable{
private String receiver;
private String subject;
private String appellation;
private String contxt;
private String tail;
public Mail (AdvTemplate advTemplate){
this.contxt = advTemplate.getAdvContext();
this.subject = advTemplate.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 getContxt() {
return contxt;
}
public void setContxt(String contxt) {
this.contxt = contxt;
}
public String getTail() {
return tail;
}
public void setTail(String tail) {
this.tail = tail;
}
@Override
public Mail clone(){
Mail mail = null;
try{
mail = (Mail)super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
}
可以看到邮件内容实际上就是定制化信息(尊称等)加上广告内容,准备完毕后,接下来就是模拟服务器发送邮件了:
public class PrototypeDesign {
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 cloneMainl = mail.clone(); //调用Mail的clone方法
mail.setAppellation(getRandString(5)+" 先生(女士) ");
mail.setReceiver(getRandString(5)+"@"+getRandString(8));
sendMail(cloneMainl);
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();
}
}
假设我们给6个VIP用户发送该邮件,邮件地址通过随机数模拟获取,理论上说,代码已经OK了,但是实际上,我们看到这是一个单线程的邮件发送,假如你要给上百万甚至更多的VIP用户发送呢?那就必须改成多线程,但是问题也出现了,发送一封邮件是需要时间的,假如第一封还没有发出去,结果,另一个线程直接把邮件的地址改了,那么这就很麻烦了,当然,你可以说,我每一封邮件都去单独new一个对象,但是,性能就大大下降了,所以,clone方法的使用自然是首选。每一个线程在处理邮件发送的时候,都会在内存中去复制一份对象然后修改邮件地址(注意这里复制的对象一定要是深拷贝),这样,所有的线程处理都互不影响了,因为每个线程处理的邮件对象都是独立的。这就是原型模式的简单应用。
三、clone的深拷贝与浅拷贝问题:
对于clone,有人就记住了一句clone方法对对象进行的是浅拷贝,实际上,这句话需要理解为,对类内部的引用类型是浅拷贝,对基本类型是深拷贝,那么,问题就出来了,到底哪些类型是引用类型,哪些是基本类型呢?我们先来看一个浅拷贝和深拷贝的demo:
我们先定义一个Address类,该类实现了Cloneable接口,我们覆写其中的clone方法;
/** 地址类:实现Cloneable接口,覆写clone方法 */
class Address implements Cloneable{
public Address(String adr) {
this.adr = adr;
}
public String getAdr() {
return adr;
}
public void setAdr(String adr) {
this.adr = adr;
}
@Override
protected Address clone() throws CloneNotSupportedException{
return (Address) super.clone();
}
private String adr;
}
然后我们再定义一个Student类,它有两个成员变量,分别是一个String类型的name和上面的类Address,同样,该类也是实现了Cloneable接口,覆写了其中的clone方法:
/** 学生类:两个成员变量,一个String,一个是类“地址” */
class Student implements Cloneable{
public Student(){
}
public Student(String name, Address address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
/* 覆写其中的clone方法 */
@Override
public Student clone() throws CloneNotSupportedException{
Student clone = (Student)super.clone();
return clone;
}
private String name;
private Address address;
}
接下来我们测试代码,先实例化一个Student,然后用过该Student去复制一个一样的对象,并对该对象的两个类变量进行重新赋值,然后打印一下结果:
public class test {
public static void main(String[] args) throws CloneNotSupportedException{
/* 先定义一个名叫“张三”的“四川”学生 */
Student std = new Student();
std.setName("张三");
std.setAddress(new Address("四川"));
/* 用std复制一个叫“李四”的“河北”学生 */
Student cloneStd = std.clone();
cloneStd.setName("李四");
cloneStd.getAddress().setAdr("河北");
System.out.println("name:" + std.getName() + ", addr:" + std.getAddress().getAdr());
System.out.println("name1:" + cloneStd.getName() + ", addr1:" + cloneStd.getAddress().getAdr());
}
}
我们执行下结果:
你会发现,“李四”才是“河北”学生啊,怎么“张三”也变成了“河北”学生呢?这就是浅拷贝,因为两个对象都是指向了同一块Address内存,为什么呢?那是因为Student类里面的Address变量是引用类型变量,引用类型变量相当于C/C++中的指针变量,如果没有对其单独开辟一个内存空间的话,那么指向的还是原来的内存区域,怎样才能对引用类型变量再开辟一个内存空间的?答案就是对引用类型变量也调用clone进行复制。注意到我们的Address也是实现了Cloneable接口吗?我们在Student类的clone中增加对引用类型Address的clone:
/** 学生类:两个成员变量,一个String,一个是类“地址” */
class Student implements Cloneable{
...
@Override
public Student clone() throws CloneNotSupportedException{
Student clone = (Student)super.clone();
clone.address = (Address)address.clone();
return clone;
}
...
}
对address 进行了clone之后,我们再执行以下客户端代码,打印如下:
这就是深拷贝,“张三”和“李四”都来自了不同的地方。可是,这就有一个问题了,String是个类啊,应该是一个引用类型的变量,怎么在类内部就被深拷贝了呢?因为编译器对其做了特殊处理(使其和基本数据类型一样),所以,在类内部,String类型就当成基本类型使用就好了,但是,其他的类变量类型,一定要确认清楚是否是引用类型,如果是的话,想要进行深拷贝,就需要对引用类型也实现clone。