场景
设计模式-原型模式-浅克隆和深克隆在Java中的使用示例:
设计模式-原型模式-浅克隆和深克隆在Java中的使用示例_霸道流氓气质的博客-CSDN博客
上面初步记录了原型模式的使用示例,下面再记录一个银行节假日或者搞活动
时发送邮件的例子。
原型模式
原型模式(Prototype Pattern)的简单程度仅次于单例模式和迭代器模式。正是由于简单,使用的场景才非常地多,
其定义如下:
Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.
(用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。)
简单,太简单了!原型模式的核心是一个clone方法,通过该方法进行对象的拷贝,Java提供了一个Cloneable接口来标示
这个对象是可拷贝的,为什么说是“标示”呢?翻开JDK的帮助看看Cloneable是一个方法都没有的,这个接口只是一个标记作用,
在JVM中具有这个标记的对象才有可能被拷贝。那怎么才能从“有可能被拷贝”转换为“可以被拷贝”呢?
方法是覆盖clone()方法,是的,你没有看错是重写clone()方法。
原型模式的优点
性能优良
原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,
原型模式可以更好地体现其优点。
逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的**。优点就是减少了约束,
缺点也是减少了约束,需要大家在实际应用时考虑。
原型模式的使用场景
资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,
然后由工厂方法提供给调用者。
原型模式的注意事项
构造函数不会被执行
一个实现了Cloneable并重写了clone方法的类A,有一个无参构造或有参构造B,通过new关键字产生了一个对象S,
再然后通过S.clone()方式产生了一个新的对象T,那么在对象拷贝时构造函数B是不会被执行的。具体可以参考示例代码。
浅拷贝和深拷贝
注意:深拷贝和浅拷贝建议不要混合使用,特别是在涉及类的继承时,父类有多个引用的情况就非常复杂,
建议的方案是深拷贝和浅拷贝分开实现。
关于浅拷贝和深拷贝可以参考示例代码。
clone与final两个冤家
对象的clone与对象内的final关键字是有冲突的,你要使用clone方法,在类的成员变量上就不要增加final关键字。
注:
博客:
霸道流氓气质的博客_CSDN博客-C#,架构之路,SpringBoot领域博主
实现
1、某银行节日发送抽奖活动通知邮件。
新建广告信模板代码
@Data
public class AdvTemplate {
//广告信名称
private String advSubject = "xx银行抽奖活动";
//广告信内容
private String advContext = "抽奖活动通知:......";
}
2、新建邮件类代码
//邮件类代码
@Data
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();
}
}
3、场景类
//场景类
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("badaoxxx版权所有");
while (i<MAX_COUNT){
//每封邮件不同的地方
mail.setAppellation(i+"先生/女士");
mail.setReceiver(i+"123@123.com");
sendMail(mail);
i++;
}
}
public static void sendMail(Mail mail){
System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t...发送成功!");
}
}
问题:
发送的单线程的,需要把sendMail修改为多线程,也会有问题,产生第一封邮件对象,放到线程一中运行,还没有发送出去;
线程2也启动了,直接就把邮件对象的的收件人和称谓给改了,线程不安全了,当然这有其它N多中解决方法,
其中一种就是使用一种新型模式来解决这个问题:通过对象的复制功能来解决这个问题。
4、修改后的邮件类
//修改后的邮件类
@Data
public class MailExtend implements Cloneable{
//收件人
private String receiver;
//邮件名称
private String subject;
//称谓
private String appellation;
//内容
private String context;
//邮件底部,一般是加上"xxx版权所有"等信息
private String tail;
public MailExtend(AdvTemplate advTemplate){
this.context = advTemplate.getAdvContext();
this.subject = advTemplate.getAdvSubject();
}
@Override
public MailExtend clone() throws CloneNotSupportedException {
MailExtend mailExtend = null;
mailExtend = (MailExtend) super.clone();
return mailExtend;
}
}
5、修改后的场景类
//修改后的场景类
public class ClientExtend {
//发送账单的数量
private static int MAX_COUNT = 6;
public static void main(String[] args) throws CloneNotSupportedException {
//模拟发送邮件
int i =0;
//把模板定义出来,这个一般从数据库中获取
MailExtend mail = new MailExtend(new AdvTemplate());
mail.setTail("badaoxxx版权所有");
while (i<MAX_COUNT){
//每封邮件不同的地方
MailExtend cloneMail = mail.clone();
mail.setAppellation(i+"先生/女士");
mail.setReceiver(i+"123@123.com");
sendMail(cloneMail);
i++;
}
}
public static void sendMail(MailExtend mail){
System.out.println("标题:"+mail.getSubject()+"\t收件人:"+mail.getReceiver()+"\t...发送成功!");
}
}
运行结果不变,而且sendMail即使是多线程也没有关系。
Client类中的mail.clone(),把对象复制一份,产生一个新的对象,和原有对象一样,然后再修改细节的数据,
如设置称谓、收件人地址等。这种不通过new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
6、验证clone之后的构造函数不会被执行
//构造函数不会被执行
public class Thing implements Cloneable{
public Thing(){
System.out.println("构造函数被执行了...");
}
@Override
public Thing clone() throws CloneNotSupportedException {
Thing thing = null;
thing = (Thing)super.clone();
return thing;
}
}
再写一个Client 进行对象的拷贝
public class TestClient {
public static void main(String[] args) throws CloneNotSupportedException {
Thing thing = new Thing();
Thing cloneThing = thing.clone();
}
}
从运行结果中看到,对象拷贝时构造函数没有被执行
Object类的clone方法的原理是从内存中以二进制流的方式进行拷贝,重新分配一个内存块。
7、浅克隆与深克隆
看一个浅克隆的例子
public class QianClone implements Cloneable{
//定义一个私有变量
private ArrayList<String> arrayList = new ArrayList<>();
@Override
public QianClone clone() throws CloneNotSupportedException {
QianClone thing = null;
thing = (QianClone)super.clone();
return thing;
}
public void setValue(String value){
this.arrayList.add(value);
}
public ArrayList<String> getValue(){
return this.arrayList;
}
}
调用浅克隆的场景类
public class QianCloneClient {
public static void main(String[] args) throws CloneNotSupportedException {
QianClone qianClone = new QianClone();
qianClone.setValue("张三");
QianClone qianCloneClone = qianClone.clone();
qianCloneClone.setValue("李四");
System.out.println(qianClone.getValue());
//[张三, 李四]
}
}
增加一个私有变量arrayList,然后运行结果不光有张三,还有李四。
是因为Object类的clone方法只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,
这种拷贝就是浅拷贝。两个对象共享了一个私有变量,你改大家都能改。那么为什么在Mail那个类中就可以使用String类型,
而不会产生由浅拷贝带来的问题。因为内部的数组和对象才不拷贝,其它的原始类型int、long、cha等以及String都会被拷贝。
深拷贝的例子
//深拷贝
public class ShenClone implements Cloneable{
//定义一个私有变量
private ArrayList<String> arrayList = new ArrayList<>();
@Override
public ShenClone clone() throws CloneNotSupportedException {
ShenClone thing = null;
thing = (ShenClone)super.clone();
thing.arrayList = (ArrayList<String>) this.arrayList.clone();
return thing;
}
public void setValue(String value){
this.arrayList.add(value);
}
public ArrayList<String> getValue(){
return this.arrayList;
}
}
深拷贝场景类
public class ShenCloneClient {
public static void main(String[] args) throws CloneNotSupportedException {
ShenClone shenClone = new ShenClone();
shenClone.setValue("张三");
ShenClone shenClone1 = shenClone.clone();
shenClone1.setValue("李四");
System.out.println(shenClone.getValue());
//[张三]
}
}
对私有的类变量进行独立的拷贝。
该方法实现了完全的拷贝,两个对象之间没有任何的瓜葛了,不互相影响,这种就是深拷贝。