一、原型模式之简单介绍:
》定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,
》特点:不需要知道任何创建的细节,不调用构造函数。
》类型:创建型。
》适用场景:
1、类初始化消耗较多资源。
2、new产生的一个对象需要非常复杂的过程(数据准备、访问权限等);
3、构造函数比较复杂;
4、循环体中产生大量对象时;
》优点:
原型模式比直接new创建对象性能要高
简化创建过程;
》缺点:
必须配备克隆方法
对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险;
深拷贝、浅拷贝使用要得当。
原型扩展:
》深克隆:
对于引用类型,如果需要他们指向不同的对象,就需要深克隆,也就是需要重写或者说
覆盖object的cloneable()方法。
》浅克隆:
浅克隆的引用类型,克隆出的新引用和原来的引用指向的对象是同一个。
二、代码实践
创建如下实例,我们要给很多人发送邮件,模板内容一致,来模拟创建大量对象。同时,通过不断改进代码的状态,来演示原型模板使用的特点。
假设一般常见的方式,代码如下:
package com.zxl.design.pattern.creation.prototype;
/**
* Created by Administrator on 2019/7/6.
*/
public class Mail {
private String name;
private String emailAddress;
private String content;
public Mail(){
System.out.println("constructor as default");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "Mail{" +
"name='" + name + '\'' +
", emailAddress='" + emailAddress + '\'' +
", content='" + content + '\'' +
'}';
}
}
package com.zxl.design.pattern.creation.prototype;
import java.text.MessageFormat;
/**
* Created by Administrator on 2019/7/6.
*/
public class MailUtil {
public static void sendMail(Mail mail){
String outputContent = "向{0}同学发送,邮件地址:{1},发送内容:{2}的邮件";
System.out.println(MessageFormat.format(outputContent,mail.getName(),mail.getEmailAddress(),mail.getContent()));
}
public static void saveOriginalRecord(Mail mail){
System.out.println("存储原始的模板:"+mail.getContent());
}
}
package com.zxl.design.pattern.creation.prototype;
/**
* Created by Administrator on 2019/7/6.
*/
public class Test {
public static void main(String[] args) {
Mail mail = new Mail();
mail.setContent("初始化模板");
for (int i=0;i<10;i++){
mail.setName("姓名"+i);
mail.setEmailAddress("邮件地址:"+i+"@address.com");
mail.setContent("恭喜您,参与我们的活动中奖了");
MailUtil.sendMail(mail);
}
MailUtil.saveOriginalRecord(mail);
}
}
默认我们每次发送一封邮件,做下记录,所以代码如上。运行结果如下:
"D:\Program Files\Java\jdk1.8.0_102\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58660,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes;D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar" com.zxl.design.pattern.creation.prototype.Test
Connected to the target VM, address: '127.0.0.1:58660', transport: 'socket'
constructor as default
向姓名0同学发送,邮件地址:邮件地址:0@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名1同学发送,邮件地址:邮件地址:1@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名2同学发送,邮件地址:邮件地址:2@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名3同学发送,邮件地址:邮件地址:3@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名4同学发送,邮件地址:邮件地址:4@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名5同学发送,邮件地址:邮件地址:5@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名6同学发送,邮件地址:邮件地址:6@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名7同学发送,邮件地址:邮件地址:7@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名8同学发送,邮件地址:邮件地址:8@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
向姓名9同学发送,邮件地址:邮件地址:9@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
存储原始的模板:恭喜您,参与我们的活动中奖了
Disconnected from the target VM, address: '127.0.0.1:58660', transport: 'socket'
Process finished with exit code 0
那在此,引入原型模式的方式,看代码有何区别:
首先,需要在mail类中添加克隆方法,其次,在测试类中,稍作更改,具体如下:
先在Mail类中添加如下方法:
@Override
protected Object clone() throws CloneNotSupportedException {
System.out.println("clone mail object");
return super.clone();
}
其次修改测试类中的代码,如下:
package com.zxl.design.pattern.creation.prototype;
/**
* Created by Administrator on 2019/7/6.
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Mail mail = new Mail();
mail.setContent("初始化模板");
for (int i=0;i<10;i++){
Mail mail1Temp = (Mail) mail.clone();
mail1Temp.setName("姓名"+i);
mail1Temp.setEmailAddress("邮件地址:"+i+"@address.com");
mail1Temp.setContent("恭喜您,参与我们的活动中奖了");
MailUtil.sendMail(mail1Temp);
}
MailUtil.saveOriginalRecord(mail);
}
}
之后,debug 方式跟踪代码执行过程,在代码初始 Mail mail = new Mail();处添加断点,然后单步跟踪。
首先一个问题:初始时肯定会调用构造器,那克隆时会增加克隆方法吗? 继续跟踪
"D:\Program Files\Java\jdk1.8.0_102\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58775,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes;D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar" com.zxl.design.pattern.creation.prototype.Test
Connected to the target VM, address: '127.0.0.1:58775', transport: 'socket'
constructor as default
clone mail object
Disconnected from the target VM, address: '127.0.0.1:58775', transport: 'socket'
Exception in thread "main" java.lang.CloneNotSupportedException: com.zxl.design.pattern.creation.prototype.Mail
at java.lang.Object.clone(Native Method)
at com.zxl.design.pattern.creation.prototype.Mail.clone(Mail.java:50)
at com.zxl.design.pattern.creation.prototype.Test.main(Test.java:11)
Process finished with exit code 1
注意,在此我继续时发现一个问题,报错如下,简单确认了下,发现是Mail类刚才忘了实现Cloneable接口,在此再次进行
在此再次运行到这里,发现通过原型模式设计模式方式设计时,发现只有第一次会执行构造器。
直接全部运行,log如下
"D:\Program Files\Java\jdk1.8.0_102\bin\java" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:58836,suspend=y,server=n -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes;D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar" com.zxl.design.pattern.creation.prototype.Test
Connected to the target VM, address: '127.0.0.1:58836', transport: 'socket'
constructor as default
clone mail object
向姓名0同学发送,邮件地址:邮件地址:0@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名1同学发送,邮件地址:邮件地址:1@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名2同学发送,邮件地址:邮件地址:2@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名3同学发送,邮件地址:邮件地址:3@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名4同学发送,邮件地址:邮件地址:4@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名5同学发送,邮件地址:邮件地址:5@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名6同学发送,邮件地址:邮件地址:6@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名7同学发送,邮件地址:邮件地址:7@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名8同学发送,邮件地址:邮件地址:8@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
clone mail object
向姓名9同学发送,邮件地址:邮件地址:9@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
存储原始的模板:初始化模板
Disconnected from the target VM, address: '127.0.0.1:58836', transport: 'socket'
Process finished with exit code 0
可以看到,上面每次都走了一次克隆方法的调用,但是构造器只是走了一次
下面,我们在mail的toString()方法中,添加一个打印内容,即super.toString(),同时,在上方Test 类中,在发送邮件之后,添加如下打印语句:
System.out.println("mailTemp"+mail1Temp);
然后运行如下
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=58916:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.Test
constructor as default
clone mail object
向姓名0同学发送,邮件地址:邮件地址:0@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名0', emailAddress='邮件地址:0@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@4554617c
clone mail object
向姓名1同学发送,邮件地址:邮件地址:1@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名1', emailAddress='邮件地址:1@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@74a14482
clone mail object
向姓名2同学发送,邮件地址:邮件地址:2@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名2', emailAddress='邮件地址:2@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@1540e19d
clone mail object
向姓名3同学发送,邮件地址:邮件地址:3@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名3', emailAddress='邮件地址:3@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@677327b6
clone mail object
向姓名4同学发送,邮件地址:邮件地址:4@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名4', emailAddress='邮件地址:4@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@14ae5a5
clone mail object
向姓名5同学发送,邮件地址:邮件地址:5@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名5', emailAddress='邮件地址:5@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@7f31245a
clone mail object
向姓名6同学发送,邮件地址:邮件地址:6@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名6', emailAddress='邮件地址:6@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@6d6f6e28
clone mail object
向姓名7同学发送,邮件地址:邮件地址:7@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名7', emailAddress='邮件地址:7@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@135fbaa4
clone mail object
向姓名8同学发送,邮件地址:邮件地址:8@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名8', emailAddress='邮件地址:8@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@45ee12a7
clone mail object
向姓名9同学发送,邮件地址:邮件地址:9@address.com,发送内容:恭喜您,参与我们的活动中奖了的邮件
mailTempMail{name='姓名9', emailAddress='邮件地址:9@address.com', content='恭喜您,参与我们的活动中奖了'}com.zxl.design.pattern.creation.prototype.Mail@330bedb4
存储原始的模板:初始化模板
Process finished with exit code 0
可以往右侧适当滑动下看,因为内容有点多。我们可以看到,mailtemp 的经过转化的和hash相关的值,每创建一次,值都不一样。
》实践二:体验原型模式另一种常用的方式,即抽象类中使用。
测试代码如下,:
package com.zxl.design.pattern.creation.prototype.abstractPrototype;
/**
* Created by Administrator on 2019/7/6.
*/
public abstract class A implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.zxl.design.pattern.creation.prototype.abstractPrototype;
/**
* Created by Administrator on 2019/7/6.
*/
public class B extends A {
public static void main(String[] args) throws CloneNotSupportedException {
B b = new B();
b.clone();
}
}
如上,进行debug 进行跟踪测试,可以发现,b 调用clone()方法时,会走进A的clone() 方法中
四、验证下深克隆与浅克隆的区别:
初级代码演示
package com.zxl.design.pattern.creation.prototype.clone;
import java.util.Date;
/**
* Created by Administrator on 2019/7/6.
*/
public class Pig implements Cloneable{
private String name;
private Date birthDay;
public String getName() {
return name;
}
public Pig(String name, Date birthDay) {
this.name = name;
this.birthDay = birthDay;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Pig{" +
"name='" + name + '\'' +
", birthDay=" + birthDay +
'}'+super.toString();
}
}
package com.zxl.design.pattern.creation.prototype.clone;
import java.util.Date;
/**
* Created by Administrator on 2019/7/6.
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date birthDay = new Date(0L);
Pig pig1 = new Pig("佩奇",birthDay);
Pig pig2 = (Pig) pig1.clone();
System.out.println(pig1);
System.out.println(pig2);
System.out.println(pig1 == pig2);
}
}
注意:如上的代码中,注意toString方法中添加了打印super.toString() 一项。运行结果如下
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=59359:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.clone.Test
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@14ae5a5
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@7f31245a
false
Process finished with exit code 0
目前,我们运行可以看到,这两个打印结果中显示,pig1 和pig2 这两个对象参数结果一致,并且是两个不同的对象。
那接下来,我们修改下pig1的birthDay属性的值,pig2不动,看下结果
package com.zxl.design.pattern.creation.prototype.clone;
import java.util.Date;
/**
* Created by Administrator on 2019/7/6.
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Date birthDay = new Date(0L);
Pig pig1 = new Pig("佩奇",birthDay);
Pig pig2 = (Pig) pig1.clone();
System.out.println(pig1);
System.out.println(pig2);
System.out.println(pig1 == pig2);
pig1.getBirthDay().setTime(6666666666666L);
System.out.println(pig1);
System.out.println(pig2);
}
}
测试结果如下:
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=59449:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.clone.Test
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@14ae5a5
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@7f31245a
false
Pig{name='佩奇', birthDay=Wed Apr 04 19:51:06 CST 2181}com.zxl.design.pattern.creation.prototype.clone.Pig@14ae5a5
Pig{name='佩奇', birthDay=Wed Apr 04 19:51:06 CST 2181}com.zxl.design.pattern.creation.prototype.clone.Pig@7f31245a
Process finished with exit code 0
注意!!!注意!!!,上方运行结果,我们只是更改了pig1 的日期,但是,看结果,pig2的运行结果也显示发生了变化!!!
这不是我们想要的!!!
分析为何会出现这样,debug走一波,把断点设置到test中第一次打印pig1的语句的地方:
如上就很清晰了,
pig1 和 pig2是两个对象不错,但问题是,他们的日期参数引用的是同一个birthday对象!!!
也就是说,克隆时,并没有克隆一个新的日期。所以,我们应该想办法让克隆时,让系统默认克隆时要克隆一份日期出来,给新的克隆对象。
在此暂停一下,如上表现就是我们所谓的浅克隆。提前预告下,我们提出的方案,就是深克隆,那如何做到深克隆呢?
原理就是,在克隆pig对象时,参数日期也要克隆一份出来。这么说来,我们在克隆方法中,把日期克隆一下,把克隆日期后的
作为结果返回就ok啦,试试!
我们将Pig类中的clone() 方法修改成如下:
@Override
protected Object clone() throws CloneNotSupportedException {
//深克隆
Pig pig = (Pig) super.clone();
pig.birthDay = (Date) pig.birthDay.clone();
//注意如下返回值,不是默认的,记得修改
return pig;
}
至此,再次运行下:
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=59697:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.clone.Test
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@14ae5a5
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@7f31245a
false
Pig{name='佩奇', birthDay=Wed Apr 04 19:51:06 CST 2181}com.zxl.design.pattern.creation.prototype.clone.Pig@14ae5a5
Pig{name='佩奇', birthDay=Thu Jan 01 08:00:00 CST 1970}com.zxl.design.pattern.creation.prototype.clone.Pig@7f31245a
Process finished with exit code 0
这次可以看到,运行时,后面两个日期中,只有pig1的日期变了
那中间的运行结果debug图也附上一份
如上,我们简单见证了原型模式的一般使用方式,那原型模式还有什么更深刻的地方吗?使用有什么注意的吗?继续往下看
五、如果原型和单例结合,会有什么影响呢?我们以简单的饿汉式单例为例,利用克隆,反射,看克隆能否破坏单例模式(即能否打破单例模式只创建一个对象的特征,而创建多个对象出来)
代码如下:
package com.zxl.design.zxl.design.pattern.singleton;
import java.io.ObjectInputStream;
import java.io.Serializable;
/**
* Created by Administrator on 2019/6/23.
*/
// 注意这里要实现克隆接口
public class HungrySingleton implements Serializable,Cloneable{
//写法简单的单例模式
//类加载时就初始化,没有多线程问题,但是可能会造成资源浪费,
//比如该对象没有被使用
private final static HungrySingleton hungrySingleton = new HungrySingleton();
//也可以将上述改成如下方式
private HungrySingleton(){
if (hungrySingleton != null){
throw new RuntimeException("单例设计模式构造器禁止反射调用");
}
}
public static HungrySingleton getHungrySingleton(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
//注意这里的克隆方法
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.zxl.design.pattern.creation.prototype.clone;
import com.zxl.design.zxl.design.pattern.singleton.HungrySingleton;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
/**
* Created by Administrator on 2019/7/6.
*/
public class Test {
public static void main(String[] args) throws CloneNotSupportedException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// Date birthDay = new Date(0L);
// Pig pig1 = new Pig("佩奇",birthDay);
// Pig pig2 = (Pig) pig1.clone();
// System.out.println(pig1);
// System.out.println(pig2);
// System.out.println(pig1 == pig2);
// pig1.getBirthDay().setTime(6666666666666L);
// System.out.println(pig1);
// System.out.println(pig2);
HungrySingleton hungrySingleton = HungrySingleton.getHungrySingleton();
//如下表示通过反射获取clone 方法
Method method = hungrySingleton.getClass().getDeclaredMethod("clone");
//打开clone方法的访问权限
method.setAccessible(true);
//通过反射中的invoke方法克隆一个单例对象
HungrySingleton cloneHungrySingleton = (HungrySingleton) method.invoke(hungrySingleton);
System.out.println(hungrySingleton);
System.out.println(cloneHungrySingleton);
System.out.println(hungrySingleton == cloneHungrySingleton);
}
}
运行结果如下:
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=59922:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.clone.Test
com.zxl.design.zxl.design.pattern.singleton.HungrySingleton@74a14482
com.zxl.design.zxl.design.pattern.singleton.HungrySingleton@1540e19d
false
Process finished with exit code 0
在此可以看到,我们成功地通过克隆创建了两个单例对象,这显然是不符合单例设计模式规则的,那怎么办呢?
单例设计模式为了防止克隆方式破坏,有两个方法:
一、单例类禁止实现克隆接口;
二、如果单例类实现了克隆接口,那我们在单例类的克隆方法中,返回对象时,直接返回getInstance() 方法的结果。
第一个我们不再测试,第二个方案按照方案修改clone() 方法代码如下,并测试:
@Override
protected Object clone() throws CloneNotSupportedException {
return getHungrySingleton();
}
查看结果
"D:\Program Files\Java\jdk1.8.0_102\bin\java" "-javaagent:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\lib\idea_rt.jar=60019:D:\InteliijIDea\IntelliJ IDEA 2017.1.4\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_102\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_102\jre\lib\rt.jar;C:\Users\Administrator\Desktop\gson-master\gson-master\DesignMode\target\classes" com.zxl.design.pattern.creation.prototype.clone.Test
com.zxl.design.zxl.design.pattern.singleton.HungrySingleton@74a14482
com.zxl.design.zxl.design.pattern.singleton.HungrySingleton@74a14482
true
Process finished with exit code 0
如上,我们验证克隆破坏单例的情形,而改进方案的有效性。
回想下,破坏单例设计模式的方案有哪些呢?
六、原型模式源码解析:
具体可自己查看因为原型模式的关键就是使用克隆方法,那可以查看下jdk源码中哪些实现了克隆接口,自己查看下。
七、关于原型模式的使用,特别注意,原型模式本身很简单,但是使用时,一定要注意克隆的对象是不是自己期待的对象,一旦用错,将埋一个大坑!所以特别注意深克隆与浅克隆的区别。