我们接着上面的接着研究:
是把sendMail()修改成多线程的问题,但是改成多线程还是有问题呀,产生第一封邮件对象,放到线程一中运行,还没有发送出去;线程二也启动了,直接就把对象mail的收件人地址称谓全都改掉了,线程就不安全了。说道这里,你会说这有n种方式去解决,其中一种就是使用一种新型模式来解决这个问题:通过对象的复制功能来解决这个问题。
我们只需要给Mail类增加一个Cloneable接口(java自带的接口),Mail实现了这个接口后,在Mail类中覆写了clone方法,我们来看Mail类的改变:
package demo;
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();
}
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;
}
<strong>@Override
public 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;
}</strong>
}
我们看粗体部分,是实现了接口覆写的方法,下面我们看一下场景类的变化:
package demo;
import java.util.Random;
import java.util.concurrent.ForkJoinPool.ManagedBlocker;
import org.omg.CORBA.PUBLIC_MEMBER;
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 cloneMail = mail.clone();
cloneMail.setAppellation(getRandString(5)+"先生");
cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");
sendMail(cloneMail);
i++;
}
}
public static void sendMail(Mail mail){
System.out.println("标题" +mail.getSubject() +"\t收件人" + mail.getReceiver() + "\t……发送成功!");
}
public static String getRandString(int maxLength){
String source = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
StringBuffer buffer = new StringBuffer();
Random random = new Random();
for(int i = 0;i<maxLength;i++){
buffer.append(source.charAt(random.nextInt(source.length())));
}
return buffer.toString();
}
}
运行结果保持不变,一样可以发送邮件,而且sendMail方法即使是使用了多线程也没有关系,注意看Client类中的mail.clone()这个方法,把对象复制一份,产生一个新的对象,和原有的对象一样,然后再修改细节的数据。这种不通过new关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
那么原型模式的优点是什么呢?
一性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
二逃避构造函数的约束
这即是它的优点也是它的缺点,直接在内存中拷贝,构造函数是不会执行的。优点是减少了约束,缺点是减少了约束,这个需要大家在实际应用时考虑。
原型模式使用场景:
资源优化场景······类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
性能和安全要求的场景······通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
一个对象多个修改者的场景······一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与java融为一体。可以随时拿来使用。
原型模式使用注意事项:
原型模式虽然简单,但是在java中使用原型模式也就是clone方法还是有一些使用事项的,下面我们通过几个例子来看一下:
·················································································································································································································································
~~~构造函数不会被执行
一个实现Cloneable并重写了clone方法的类A,有一个无参构造或有参构造B,通过new关键字产生一个对象S,再然后通过S.clone()产生一个新的对象T,那么在对象拷贝时构造函数B是不会执行的。我们来看一下小demo:
可拷贝对象:
package demo;
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return thing;
}
}
看一下Client类:
package demo;
public class Client {
public static void main(String[] args){
//产生一个对象
Thing thing = new Thing();
//拷贝一个对象
Thing cloneThing = thing.clone();
}
}
运行结果:
构造函数被执行了……
对象拷贝时,构造方法确实没有执行,这点从原理上可以将的通,Object类的clone方法的原理就是从内存中(具体说是堆内存) 以二进制的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行是非常正常的了。