什么是原型模式
原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。
西游记中的孙悟空拔毛变小猴,孙悟空这种根据自己的形状复制出多个身外化身的技巧,在面向对象软件设计领域被称为原型模式。孙悟空就是原型对象。
原型模式主要解决的问题
如果创建对象的成本比较大,比如对象中的数据是经过复杂计算才能得到,或者需要从RPC接口或者数据库等比较慢的IO中获取,这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作。
原型模式结构
原型模式包含如下角色:
抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口。
具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。
客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象。由于客户类针对抽象原型类Prototype编程。因此用户可以根据需要选择具体原型类,系统具有较好的扩展性,增加或者替换具体原型类都比较方便。
实现原理:
使用到了对象的拷贝,关于对象的深浅拷贝,请观看我的另外一篇文章《对象的深浅拷贝》。
原型模式应用实例
需求:模拟某银行电子账单系统的广告信发送功能,广告信的发送都是有一个模板的,从数据库查出客户的信息,然后放到模板中生成一份完整的邮件,然后交给发送机进行发送处理。
一般的实现
类图:
实现代码:
package main.java.cn.test.prototype.V1;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:34:38
* @description 广告信模板代码
*/
public class AdvTemplate {
//广告信名称
private String advSubject = "xx银行本月还款达标,可抽iPhone13等好礼!";
//广告信内容
private String advContext = "达标用户请在2022年3月1日到2022年3月30参与抽奖......";
public String getAdvSubject() {
return advSubject;
}
public void setAdvSubject(String advSubject) {
this.advSubject = advSubject;
}
public String getAdvContext() {
return advContext;
}
public void setAdvContext(String advContext) {
this.advContext = advContext;
}
}
package main.java.cn.test.prototype.V1;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:35:46
* @description 邮件类
*/
public class Mail {
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//邮件内容
private String context;
//邮件尾部, 一般是"xxx版权所有"等信息
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;
}
}
package main.java.cn.test.prototype.V1;
import java.util.Random;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:37:00
* @description 业务场景类
*/
public class Client {
//发送信息的是数量,这个值可以从数据库获取
private static int MAX_COUNT = 6;
//发送邮件
public static void sendMail(Mail mail) {
System.out.println("标题: " + mail.getSubject() + "\t 收件人:" + mail.getReceiver()
+ "\t..发送成功!");
}
public static void main(String[] args) {
//模拟邮件发送
int i = 0;
//把模板定义出来,数据是从数据库获取的
Mail mail = new Mail(new AdvTemplate());
mail.setTail("xxx银行版权所有");
while (i < MAX_COUNT) {
//下面是每封邮件不同的地方
mail.setAppellation(" 先生 (女士)");
Random random = new Random();
int num = random.nextInt(9999999);
mail.setReceiver(num + "@" + "liuliuqiu.com");
//发送 邮件
sendMail(mail);
i++;
}
}
}
运行结果:
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:3157398@liuliuqiu.com ..发送成功!
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:7278846@liuliuqiu.com ..发送成功!
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:5923335@liuliuqiu.com ..发送成功!
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:9352553@liuliuqiu.com ..发送成功!
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:1480251@liuliuqiu.com ..发送成功!
标题: xx银行本月还款达标,可抽iPhone13等好礼! 收件人:9782860@liuliuqiu.com ..发送成功!
上面的代码存在的问题:
1)发送邮件需要重复创建Mail类对象,而且Mail类的不同对象之间差别非常小,这样重复的创建操作十分的浪费资源。
2)这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作。
使用原型模式实现
实现代码:
package main.java.cn.test.prototype.V2;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:34:38
* @description 广告信模板代码
*/
public class AdvTemplate {
//广告信名称
private String advSubject = "xx银行本月还款达标,可抽iPhone13等好礼!";
//广告信内容
private String advContext = "达标用户请在2022年3月1日到2022年3月30参与抽奖......";
public String getAdvSubject() {
return advSubject;
}
public void setAdvSubject(String advSubject) {
this.advSubject = advSubject;
}
public String getAdvContext() {
return advContext;
}
public void setAdvContext(String advContext) {
this.advContext = advContext;
}
}
package main.java.cn.test.prototype.V2;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:43:21
* @description 邮件类 实现Cloneable接口,表示该类的实例可以被复制
*/
public class Mail implements Cloneable {
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//邮件内容
private String context;
//邮件尾部, 一般是"xxx版权所有"等信息
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;
}
@Override
public Mail clone() {
Mail mail = null;
try {
mail = (Mail) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return mail;
}
}
package main.java.cn.test.prototype.V2;
import java.util.Random;
/**
* @author ningzhaosheng
* @date 2023/6/12 18:45:02
* @description
*/
public class Client {
//发送信息的是数量,这个值可以从数据库获取
private static int MAX_COUNT = 6;
//发送邮件
public static void sendMail(Mail mail) {
System.out.println("标题: " + mail.getSubject() + "\t 收件人: " + mail.getReceiver()
+ "\t..发送成功!");
}
public static void main(String[] args) {
//模拟邮件发送
int i = 0;
//把模板定义出来,数据是从数据库获取的
Mail mail = new Mail(new AdvTemplate());
mail.setTail("xxx银行版权所有");
while (i < MAX_COUNT) {
//下面是每封邮件不同的地方
Mail cloneMail = mail.clone();
cloneMail.setAppellation(" 先生 (女士)");
Random random = new Random();
int num = random.nextInt(9999999);
cloneMail.setReceiver(num + "@" + "liuliuqiu.com");
//发送 邮件
sendMail(cloneMail);
i++;
}
}
}
原型模式的优点
1)当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
2)原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。
3)可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作。
原型模式缺点
需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则。
使用场景
1)资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。
2)复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。
3)性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使
用原型模式。
4)同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。
5)需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。