当前位置:   article > 正文

设计模式-原型模式(Prototype Pattern)_原型模式(prototype pattern)克隆person、weapon

原型模式(prototype pattern)克隆person、weapon

推荐:Java设计模式汇总

原型模式

定义
使用原型实例指定待创建的类型,并且通过复制这个原型实例来创建新的对象。

类型
创建型。

例子
Mail类(邮件类),实现了Cloneable接口(只有实现了该接口的类的实例才可以调用clone()方法克隆实例,否则会抛出异常)。

package com.kaven.design.pattern.creational.prototype;

public class Mail implements Cloneable{
    private String name;
    private String emailAddress;
    private String content;
    public Mail(){
        System.out.println("Mail Class Constructor");
    }

    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 + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        System.out.println("clone mail object");
        return super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

Cloneable接口只是起到告诉程序这个类的实例可以调用clone方法的作用,它本身并没有定义任何方法。
源码如下:

package java.lang;
public interface Cloneable {
}

  • 1
  • 2
  • 3
  • 4

MailUtil类(邮件的工具类),调用sendMail()方法可以发送邮件。

package com.kaven.design.pattern.creational.prototype;

import java.text.MessageFormat;

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()));
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

应用层代码:

package com.kaven.design.pattern.creational.prototype;

public class Test {
    private static String[] name = {"yy","kaven","jack","jojo"};
    public static void main(String[] args) throws CloneNotSupportedException {
        Mail mail = new Mail();
        mail.setContent("初始化模板");
        for (int i = 0; i < name.length; i++) {
            Mail mailTemp = (Mail) mail.clone();
            mailTemp.setName(name[i]);
            mailTemp.setEmailAddress(name[i]+"@lkwyy.com");
            mailTemp.setContent("恭喜您,此次活动中奖了");
            MailUtil.sendMail(mailTemp);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结果:

Mail Class Constructor
clone mail object
向 yy 同学,邮件地址:yy@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 kaven 同学,邮件地址:kaven@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 jack 同学,邮件地址:jack@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
clone mail object
向 jojo 同学,邮件地址:jojo@lkwyy.com,邮件内容:恭喜您,此次活动中奖了,发送邮件成功
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Mail mail = new Mail()会调用构造器创建原型实例,调用构造器时会输出Mail Class Constructor,而每次要发送的邮件都是对这个原型实例的克隆Mail mailTemp = (Mail) mail.clone();,并且可以发现clone()方法不会调用构造器,对象拷贝时构造器是不会被执行的,Object类的clone()方法的原理是从内存中(具体的说,就是堆内存中)以二进制流的方式进行拷贝,并且会重新分配一个内存块(也是堆内存中),也就不需要调用构造器了
这里我们便实现了一个简单的原型模式例子,接下来我们分析一下克隆的问题。

深克隆与浅克隆

package com.kaven.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Pig implements Cloneable {
    private String name;
    private Date birthday;

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

测试1:

package com.kaven.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Pig pig = new Pig("佩奇",new Date(0L));
        Pig clonePig = (Pig) pig.clone();

        System.out.println(pig);
        System.out.println(clonePig);
        System.out.println(pig == clonePig);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

输出:

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
false
  • 1
  • 2
  • 3

很显然它们不是指向同一个实例,因为会重新分配一个内存块。

测试2:

package com.kaven.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Pig pig = new Pig("佩奇",new Date(0L));
        Pig clonePig = (Pig) pig.clone();

        System.out.println(pig);
        System.out.println(clonePig);

        clonePig.getBirthday().setTime(1000000000000L);
        System.out.println(pig);
        System.out.println(clonePig);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出:

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
  • 1
  • 2
  • 3
  • 4

为什么我们改变clonePig的birthday属性,pig的birthday属性也会进行改变?
我们来Debug一下。
打上断点:
在这里插入图片描述
开始Debug。
在这里插入图片描述
Debug后,可以发现pig(Pig@616)和clonePig(Pig@617)确实不是指向同一个实例,但它们的birthday属性却是指向相同的Date实例(Date@621),这就是浅克隆,所以,当修改clonePig的birthday属性时,pig的birthday属性也会进行改变。
在这里插入图片描述
从上图可以看出,当修改了clonePig的birthday属性时clonePig.getBirthday().setTime(1000000000000L),pig的birthday属性也会进行改变。

如何改进成深克隆呢?
改进如下:

package com.kaven.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Pig implements Cloneable {
    private String name;
    private Date birthday;

    public Pig(String name, Date birthday) {
        this.name = name;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "Pig{" +
                "name='" + name + '\'' +
                ", birthday=" + birthday +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Pig clonePig = (Pig) super.clone();

        //深克隆
        clonePig.birthday = (Date) clonePig.birthday.clone();
        return clonePig;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

测试:

package com.kaven.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Pig pig = new Pig("佩奇",new Date(0L));
        Pig clonePig = (Pig) pig.clone();

        System.out.println(pig);
        System.out.println(clonePig);

        clonePig.getBirthday().setTime(1000000000000L);
        System.out.println(pig);
        System.out.println(clonePig);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

结果:

Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Thu Jan 01 08:00:00 CST 1970}
Pig{name='佩奇', birthday=Sun Sep 09 09:46:40 CST 2001}
  • 1
  • 2
  • 3
  • 4

从测试结果上来看,当修改clonePig的birthday属性时,pig的birthday属性并没有进行改变。
这就改进成深克隆了吗?是的,这便是深克隆。
其实我们还依赖了Date类本身就实现了深克隆,我们才会如此方便,Date类的clone()方法源码如下:

    public Object clone() {
        Date d = null;
        try {
            d = (Date)super.clone();
            if (cdate != null) {
                d.cdate = (BaseCalendar.Date) cdate.clone();
            }
        } catch (CloneNotSupportedException e) {} // Won't happen
        return d;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

有些引用型没有这种浅克隆的问题,如String或Integer等包装类型,大家可以自己试一试。
还有就是,final修饰的成员变量是不能进行深度拷贝的

适用场景

  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
  • 通过new产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模式。
  • 难以根据类生成实例时。
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
  • 想解耦框架与生成的实例时。

优点

  • 原型模式提供了简化的创建结构,常常与工厂方法模式一起使用。
  • 可以使用深克隆的方式保存对象的状态,在操作过程中可以追溯操作日志,做撤销和回滚操作。
  • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率。

缺点

  • 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
  • 在做深克隆的时候,如果对象之间存在多重嵌套的引用时,为了实现克隆,对每一层对象对应的类都必须支持深克隆,实现起来比较麻烦。

如果有说错的地方,请大家不吝赐教(记得留言哦~~~~)。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/68497
推荐阅读
相关标签
  

闽ICP备14008679号