示例说原型模式
又到了一年一度最火热最繁忙的秋招,每天都会收到各个公司各种各样的短信或邮件,你是否层认真的思考过,公司是如何给你发送这条短信或者这封邮件的呢???
有的人可能会说,hr小姐姐们一条一条编辑的啊。但是真的是这样吗???
那有人又说了,我直接将短信的大致内容编辑成一个模板交给代理商,代理商会给我解决的,但是你有想过代理商又是怎么给你发送短信或邮件的呢??
假如这个任务交给你,你怎么来解决呢???
有人说很简单,看我的:
import java.util.Random;
public class Test {
//发送邮件数量
private static int MAXCOUNT = 100;
public static void main(String[] args) {
//定义邮件模板
Mail mail = new Mail(new MailTemplate());
mail.setTail("xxx公司版权所有");
for (int i = 0; i < MAXCOUNT; i++) {
mail.setAppellation(getRandString(5) + " 先生(女士)");
mail.setReceiver(getRandString(5) + "@" + getRandString(3) + ".com");
sendMail(mail);
}
}
private static void sendMail(Mail mail) {
System.out.println("主题:" + mail.getSubject() + "\t收件人:" + mail.getReceiver() + "\t...发送成功!");
}
private static String getRandString(int maxLength) {
String source = "qwertyuioplkjhgfdsazxcvbnmMNBVCXZASDFGHJKLPOIUYTREWQ";
StringBuffer sb = new StringBuffer();
Random random = new Random();
for (int i = 0; i < maxLength; i++) {
sb.append(source.charAt(random.nextInt(source.length())));
}
return sb.toString();
}
}
class MailTemplate {
//邮件主题
private String mailSubject = "xxx公司招聘";
//邮件内容
private String mailContext = "我们是xxx互联网公司,目前公司就以下岗位非常缺人。。。。,我们期待你的加入!";
public String getMailSubject() {
return mailSubject;
}
public String getMailContext() {
return mailContext;
}
}
class Mail {
//收件人
private String receiver;
//邮件主题
private String subject;
//称谓
private String appellation;
//邮件内容
private String context;
//邮件尾部
private String tail;
public Mail(MailTemplate template) {
this.context = template.getMailContext();
this.subject = template.getMailSubject();
}
public String getReceiver() {
return receiver;
}
public String getSubject() {
return subject;
}
public String getAppellation() {
return appellation;
}
public String getContext() {
return context;
}
public String getTail() {
return tail;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public void setSubject(String subject) {
this.subject = subject;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public void setContext(String context) {
this.context = context;
}
public void setTail(String tail) {
this.tail = tail;
}
}
咋一看,嗯,还不错,基本实现了邮件发送,但是这是单个线程在运行,假如发送一封邮件需要0.02秒,那发600万封邮件需要33个小时,也就是一整天都发送不完,今天没发完,明天账单又产生了,日积月累,激起了客户的不满,那咋办??
有人说,那好办,我把sendMail修改为多线程。但是只把sendMail修改成多线程还是有问题啊,产生第一封邮件放到线程1中运行,还没有发送出去呢,线程2也启动了,直接把邮件对象mail的收件人地址和称谓改了,线程不安全了。说到线程不安全,你可能会想到加锁等等一系列解决措施,但是加锁是极其影响性能的。
那用啥方式来解决这个问题呢???这就是我们今天要说的原型模式:通过对象的复制功能来解决这个问题。先来看看代码,再来细讲。
class Mail implements Cloneable {
//收件人
private String receiver;
//邮件主题
private String subject;
//称谓
private String appellation;
//邮件内容
private String context;
//邮件尾部
private String tail;
public Mail(MailTemplate template) {
this.context = template.getMailContext();
this.subject = template.getMailSubject();
}
public String getReceiver() {
return receiver;
}
public String getSubject() {
return subject;
}
public String getAppellation() {
return appellation;
}
public String getContext() {
return context;
}
public String getTail() {
return tail;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public void setSubject(String subject) {
this.subject = subject;
}
public void setAppellation(String appellation) {
this.appellation = appellation;
}
public void setContext(String context) {
this.context = context;
}
public void setTail(String tail) {
this.tail = tail;
}
@Override
protected Mail clone() throws CloneNotSupportedException {
Mail mail = (Mail) super.clone();
return mail;
}
@Override
public String toString() {
return "Mail{" +
"receiver='" + receiver + '\'' +
", subject='" + subject + '\'' +
", appellation='" + appellation + '\'' +
", context='" + context + '\'' +
", tail='" + tail + '\'' +
'}';
}
}
在Mail中,我们实现了Cloneable接口,重写了clone方法。下面再来看看Test类的变化。
public static void main(String[] args) throws CloneNotSupportedException {
//定义邮件模板
Mail mail = new Mail(new MailTemplate());
mail.setTail("xxx公司版权所有");
for (int i = 0; i < MAXCOUNT; i++) {
Mail cloneMail = mail.clone();
System.out.println(cloneMail);
cloneMail.setAppellation(getRandString(5) + " 先生(女士)");
cloneMail.setReceiver(getRandString(5) + "@" + getRandString(3) + ".com");
sendMail(cloneMail);
}
}
}
运行结果不变,一样完成邮件的发送,而sendMail即使是多线程也没有关系。看main方法里边的for循环,每次我们都通过mail的clone方法,把对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节数据。这种通过对象复制来产生新对象的模式就叫原型模式。其类图结构如下:
原型模式的核心是一个clone方法,通过该方法进行对象拷贝,java提供了一个Cloneable接口来标识这个对象是可拷贝的,为什么说是“标识”呢?因为Cloneable类中一个方法也没有,这个接口只是一个标记作用,在JVM中,只有具有这个标记的对象才有可能被拷贝。那怎么样才能从“有可能被拷贝”到“可以被拷贝”呢??方法是覆盖clone方法,即重写clone方法。
原型模式的优点
1. 原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体里边产生大量对象的时候,原型模式可以更好的体现其优点。
2. 逃避构造函数的约束,直接在内存中拷贝,构造函数是不会执行的。
原型模式使用场景
1.资源优化:类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等。
2. 性能和安全要求:通过new产生一个对象需要非常频繁的数据准备或者访问权限,则可以使用原型模式。
3. 一个对象多个修改者的场景。一个对象需要提供给其他对象访问,而各个调用者可能都需要修改其值,可以考虑使用原型模式。一般在项目中,原型模式很少单独出现,一般和工厂方法模式一起出现。
使用原型模式需要注意的问题
1.构造函数不会被执行;
2. 深拷贝和浅拷贝。关于深拷贝和浅拷贝可查看博文https://blog.csdn.net/u014727260/article/details/55003402;
3.clone与final:对象的clone和对象内的final关键字是有冲突的,final类型的数据是不支持重新赋值的,要实现深拷贝就需要去除final关键字,如图所示: