java设计模式之原型模式、浅克隆和深克隆区别、克隆破坏单例模式、原型模式源码解析

一、原型模式之简单介绍:

》定义:指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,

》特点:不需要知道任何创建的细节,不调用构造函数。

》类型:创建型。

》适用场景:

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源码中哪些实现了克隆接口,自己查看下。

七、关于原型模式的使用,特别注意,原型模式本身很简单,但是使用时,一定要注意克隆的对象是不是自己期待的对象,一旦用错,将埋一个大坑!所以特别注意深克隆与浅克隆的区别。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值