当前位置:   article > 正文

原型模式(深克隆、浅克隆)

原型模式

上期回顾:建造者模式

在这里插入图片描述



一、原型模式定义

原型模式是一种创建型设计模式,Prototype模式允许一个对象再创建另外一个可定制的对象,根本无需知道任何如何创建的细节,工作原理是:通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建。

二、原型模式的结构与实现

原型模式有以下三种角色:
1.抽象原型类(Prototype):规定了具体原型对象必须实现的接口。
2.具体原型类(Realizetype):实现抽象原型类的 clone() 方法,它是可被复制的对象。
3.访问类(PrototypeTest):使用具体原型类中的 clone() 方法来复制新的对象
在这里插入图片描述

原型模式的克隆可以分为浅克隆深克隆

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

Java中的Object类中提供clone()方法来实现浅克隆。Cloneable接口就是抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。

原型模式设计如下:

//在这里抽象原型类是Cloneable接口
//具体原型类
public class Realizetype implements Cloneable{

    public Realizetype() {
        System.out.println("具体原型对象已创建成功!");
    }

    @Override
    protected Realizetype clone() throws CloneNotSupportedException {
        System.out.println("具体原型已复制成功!");
        return (Realizetype) super.clone();
    }
}
//访问类
public class Client {
    public static void main(String[] args) throws CloneNotSupportedException {
        //创建一个原型类对象
        Realizetype realizetype = new Realizetype();
        //得到原型对象的克隆对象
        Realizetype clone = realizetype.clone();
        //查看是否是同一个对象
        System.out.println("原型对象和克隆的对象是否是同一个对象:"+(realizetype == 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

测试结果:
具体原型对象已创建成功!
具体原型已复制成功!
原型对象和克隆的对象是否是同一个对象:false

这里展示的原型模式只是一个简单的克隆,抽象原型类就是Cloneable接口具体原型类就是可被复制的对象,而访问类就是使用克隆clone()来复制新的对象

需要注意的是,单看测试结果我们会发现原型模式有以下几个问题。

  • 克隆底层不是通过new对象实现的,因为调用clone()的时候并没有打印出Realizetype 类的构造函数中的内容。
  • 原型对象和克隆的对象不是同一个对象

而且查看clone()源码发现它是一个native本地方法,也是说它并不是由java语言实现的。

 protected native Object clone() throws CloneNotSupportedException;
  • 1

三、案例

用原型模式来实现以下需求:
假如某学校要对这一学期的社团指导老师颁发聘书,那么我现在就可以根据奖状模板进行克隆,用原型模式设计思想实现。

原型对象:奖状类

//原型对象:聘书
public class Letter implements Cloneable{

    private String name;  //指导老师姓名

    public String getName() {
        return name;
    }

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

    public void show(){
        System.out.println("兹聘请 "+name+" 老师为 xxx 社团指导老师!");
    }

    @Override
    protected Letter clone() throws CloneNotSupportedException {
        return (Letter)super.clone();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

访问类:测试

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Letter letter = new Letter();
        //开始克隆
        Letter letter1 = letter.clone();
        //原型对象
        System.out.print("原型对象:");
        letter.setName("张三");
        letter.show();
        //克隆对象
        System.out.print("克隆后对象:");
        letter1.setName("李四");
        letter1.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

运行结果:

原型对象: 兹聘请 张三 老师为 xxx 社团指导老师!
克隆后对象: 兹聘请 李四 老师为 xxx 社团指导老师!

这个例子比较简单好理解,现在我在这个案例中引入深克隆和浅克隆的概念。

(一)浅克隆

浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。

1、首先我再创建一个社团类,里面有一个社团名称的属性

//社团类
public class Association {
    private String name ;  //社团名称

    public Association(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2、修改聘书类,加上一个社团类的对象属性

//原型对象:聘书
public class Letter implements Cloneable{

    private String name;  //指导老师姓名
    private Association association;  //社团

    public Letter(String name, Association association) {
        this.name = name;
        this.association = association;
    }

    public String getName() {
        return name;
    }

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

    public Association getAssociation() {
        return association;
    }

    public void setAssociation(Association association) {
        this.association = association;
    }

    public void show(){
        System.out.println("兹聘请 "+name+" 老师为 "+association.getName()+" 社团指导老师!");
    }

    @Override
    protected Letter clone() throws CloneNotSupportedException {
        return (Letter)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

测试类:将原型对象聘书类进行克隆,然后打印出原型对象中Association引用地址和克隆后Association引用地址,判断该引用地址是否发生改变

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Association association = new Association("歌舞社");
        //拷贝之前的对象
        Letter letter = new Letter("张三",association);
        System.out.print("克隆前对象:");
        letter.show();
        //拷贝之后的对象
        Letter letter1 = letter.clone();
        System.out.print("克隆后对象:");
        letter1.show();
        //判断
        System.out.println(letter.getAssociation() == letter1.getAssociation());
        System.out.println("克隆前Association对象的hashCode:"+letter.getAssociation().hashCode());
        System.out.println("克隆后Association对象的hashCode:"+letter1.getAssociation().hashCode());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

输出结果:

克隆前对象:兹聘请 张三 老师为 歌舞社 社团指导老师!
克隆后对象:兹聘请 张三 老师为 歌舞社 社团指导老师!
true
克隆前Association对象的hashCode:666988784
克隆后Association对象的hashCode:666988784

结果发现,letter.getAssociation() == letter1.getAssociation()结果返回true,说明克隆前后Letter类中持有的Association引用地址没有发生改变,加上show()内容一样,说明属性也没发生改变,这明显符合我们对浅克隆的定义。而且打印出来的hashCode值相同,同样证明了它们是引用的同一个地址。

由于指向同一个地址,那我对克隆后的对象修改属性,原来的原型对象中的属性也会发生变化吗?
测试如下,将克隆的对象中原来的张三指导的“歌舞社”改为“足球社”,再查看未克隆前的原型对象是否发生了改变。

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Association association = new Association("歌舞社");
        Letter letter = new Letter("张三",association);
        //原来
        System.out.println("原对象:");
        letter.show();
        //克隆并修改
        Letter clone = letter.clone();
        Association association1 = clone.getAssociation();
        association1.setName("足球社");
        //再次查看原来对象
        System.out.println("克隆对象修改后再次查看原来对象:");
        letter.show();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

测试结果:

原对象:
兹聘请 张三 老师为 歌舞社 社团指导老师!


克隆对象修改后再次查看原来对象:
兹聘请 张三 老师为 足球社 社团指导老师!

发现修改克隆对象中的属性,原对象也发生了同步改变。这再一次说明了浅克隆中,会把原型对象中成员变量为引用类型的引用地址也复制给克隆对象,且此引用对象的地址是共享给原型对象和克隆对象的。

总结:浅克隆只复制指向某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存,修改对象会改到原对象

(二)深克隆

清楚了浅克隆例子后。再来回顾一下深克隆的定义 ---- 创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

这要如何实现?很简单>

让Association类实现Cloneable接口,重写clone()方法

@Override
protected Association clone() throws CloneNotSupportedException {
    return (Association) super.clone();
}
  • 1
  • 2
  • 3
  • 4

修改Letter类的clone()方法,加上对association的克隆

@Override
protected Letter clone() throws CloneNotSupportedException {
    Letter letter = (Letter)super.clone();
    //拷贝Association对象
    letter.association= this.association.clone();
    return letter;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

测试结果:

原对象:
兹聘请 张三 老师为 歌舞社 社团指导老师!

克隆后对象:
兹聘请 张三 老师为 足球社 社团指导老师!

克隆对象修改后再次查看原来对象:
兹聘请 张三 老师为 歌舞社 社团指导老师!

经过这一顿操作后,修改克隆后对象的属性,也不会影响到原对象,这就是深克隆,它将原型对象中的所有类型,无论是值类型还是引用类型,都复制了一份给克隆对象。

总结:深克隆对原型对象完全拷贝,但新对象跟原对象不共享内存,修改新对象不会改变原对象。

(三)浅克隆和深克隆的区别

最后用一张图简单的总结一下浅克隆和深克隆的区别
在这里插入图片描述

四、原型模式使用场景

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

它的使用场景如下:

1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

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

闽ICP备14008679号