寂然解读设计模式 - 原型模式(下)

简介: 本章内容,我们对原型模式进行深入一点的讨论,我们来聊聊深拷贝和浅拷贝

I walk very slowly, but I never walk backwards 

设计模式 - 原型模式(下)


寂然

大家好~,我是寂然,本节课呢,我们对原型模式进行深入一点的讨论,我们来聊聊深拷贝和浅拷贝

前情提要

上节课,我们聊了聊小李求职的事情,写了一个关于原型模式的案例,大家有没有发现,我们使用原型模式创建类 Resume 的对象时,类 Resume 的属性,都是 String 类型,换而言之,都是简单类型,那假设,现在增加了一个属性,简历上需要有一个证明人,是 Witness 类型的对象,那大家一起来思考这样一个问题,当再次进行原型拷贝的时候,属性 witness 会怎么去处理呢?是复制一份,还是让一个引用去指向已有的 witness?觉知此事要躬行,下面,我们通过案例代码来进行测试

案例演示

//简历类
public class Resume implements Cloneable{

    private String name;

    private String position;

    private String salary;

    public Witness witness; //证明人

    //构造器/get/set/toString...省略
}

//证明人类
public class Witness {

    private String name;

    private String job; //职务

    //构造器/get/set/toString...省略
}

//使用原型模式测试
public class Client {

    public static void main(String[] args) {

        Resume resume = new Resume("小李", "海淀区", "面议");

        resume.setWitness(new Witness("三叔","养猪场CTO"));

        Resume clone = (Resume)resume.clone(); //使用原型模式克隆两份

        Resume clone1 = (Resume)resume.clone();

        Resume clone2 = (Resume)resume.clone();

        System.out.println(clone.getWitness().hashCode()); //测试

        System.out.println(clone1.getWitness().hashCode());

        System.out.println(clone2.getWitness().hashCode());

    }
}

可以看到,我们使用原型模式克隆了三份,发现三个成员变量 witness 的哈希值是相同的,也就是说,默认并没有对属性 witness 进行拷贝,而是通过一个引用去指向已有的 witness,这种情况就称为浅拷贝

浅拷贝

下面,我们来一起看一下浅拷贝的介绍

对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,就是将该属性值复制一份给新的对象


对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象,实际上两个对象的该成员变量都指向同一个实例,这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值

所以,我们前面讲的小李求职的案例就是浅拷贝,浅拷贝就是使用默认的 clone() 方法实现的

那如果说,实际情况中,我们希望对于数据类型是引用数据类型的成员变量,也复制一份呢?那对比着再来聊一下深拷贝,以及深拷贝的实现方式

深拷贝

深拷贝的基本介绍如下

复制对象的所有基本数据类型的成员变量值

为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象,也就是说,对象进行深拷贝要对整个对象(包括对象的引用类型)进行拷贝

深拷贝在实际开发中有两种实现方式,一种是通过重写 clone () 方法实现,一种是通过对象序列化实现

实现方式一:重写 clone() 方法

示例代码如下图所示

public class DeepCloneableTarget implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String cloneName;

    private String cloneClass;

    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性都是String,所以我们使用默认的clone方法完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class DeepProtoType implements Serializable, Cloneable {

    private String name; //String类型

    private DeepCloneableTarget deepCloneableTarget; //引用类型

    public DeepProtoType() {
        super();
    }

    //深拷贝 - 方式一:重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {

        Object deep = null;

        //这里完成的是对基本数据类型以及String类型的拷贝
        deep = super.clone();

        //对引用类型的属性进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType) deep;

        deepProtoType.deepCloneableTarget= (DeepCloneableTarget) deepCloneableTarget.clone();

        return deepProtoType;
    }
}

public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "小李";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李","克隆类");

        //方式一完成深拷贝
        DeepProtoType clone = (DeepProtoType) deepProtoType.clone();

        DeepProtoType clone1 = (DeepProtoType) deepProtoType.clone();

        System.out.println(clone.deepCloneableTarget.hashCode());

        System.out.println(clone1.deepCloneableTarget.hashCode()); //哈希值不一样

    }
}

可以看到,经测试,哈希值不一样,说明确实整个对象(包括对象的引用类型)都进行了拷贝 ,实现了深拷贝

重写 clone() 方法完成深拷贝的思路就是,分步进行,在 clone() 方法里,首先调用 super.clone() 完成对基本类型和 String 类型的拷贝,因为对于基本数据类型的成员变量,深拷贝和浅拷贝的处理方式一致,都是将该属性值复制一份给新的对象,接着我们对引用类型的属性进行单独处理,同样使用 clone() 方法完成属性值的拷贝


那这样有的小伙伴要问了,如果 DeepProtoType 有很多个引用类型的成员属性,都要这样挨个使用 clone() 方法完成属性值的拷贝嘛?是的,这种情况下,每个引用类型需要单独处理,那还有的小伙伴问了,如果引用类型是个类,里面仍然有引用类型的成员属性呢?

级联处理演示

如果引用类型是个class,类型,里面仍然有引用类型的成员属性同样,核心思想是分布单独处理,假设类 DeepProtoType 里有成员属性 deepCloneableTarget ,而DeepCloneableTarget 类中有成员属性 witness ,同样使用方式一,重写 clone() 方法完成深拷贝,示例代码如下图所示

//方式一升级:测试级联克隆
public class DeepProtoType implements Serializable, Cloneable {

    public String name; //String类型

    public DeepCloneableTarget deepCloneableTarget; //引用类型

    public DeepProtoType() {
        super();
    }

    //深拷贝 - 方式一:重写clone方法  级联

    @Override
    protected Object clone() throws CloneNotSupportedException {

        Object deep = null;

        //这里完成的是对基本数据类型以及String类型的拷贝
        deep = super.clone();

        //对引用类型的属性进行单独处理
        DeepProtoType deepProtoType = (DeepProtoType) deep;

        deepProtoType.deepCloneableTarget= (DeepCloneableTarget) deepCloneableTarget.clone();

        deepProtoType.deepCloneableTarget.witness = (Witness) deepProtoType.deepCloneableTarget.witness.clone();

        return deepProtoType;
    }

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

public class DeepCloneableTarget implements Serializable, Cloneable {

    private static final long serialVersionUID = 1L;

    private String cloneName;

    private String cloneClass;

    public Witness witness; //增加引用类型

    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性都是String,所以我们使用默认的clone方法完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "DeepCloneableTarget{" +
                "cloneName='" + cloneName + '\'' +
                ", cloneClass='" + cloneClass + '\'' +
                ", witness=" + witness +
                '}';
    }
}

//证明人类
public class Witness implements Serializable,Cloneable {

    private String name;

    private String job; //职务

    public Witness(String name, String job) {
        this.name = name;
        this.job = job;
    }

    public String getName() {
        return name;
    }

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

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//方式一升级:测试级联克隆
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "小李";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李","克隆类");

        deepProtoType.deepCloneableTarget.witness = new Witness("证明人", "职务");

        DeepProtoType clone = (DeepProtoType) deepProtoType.clone();

        DeepProtoType clone1 = (DeepProtoType) deepProtoType.clone();

        System.out.println(clone);

        System.out.println(clone1);

        System.out.println(clone.deepCloneableTarget.witness.hashCode());

        System.out.println(clone1.deepCloneableTarget.witness.hashCode());
        //测试哈希值不一样  实现了深拷贝

    }
}

实现方式二:通过对象序列化

上面我们演示了方式一,实现了深拷贝,但同时也带来了问题,如果有很多个引用类型的成员属性,或者说(引用类型是个类,里面仍然有引用类型的成员属性)这种级联的情况,需要分步处理,如果要拷贝的对象结构复杂,实现起来非常繁琐,那有没有可以一步到位的方法呢?下面我们来演示第二种,使用对象序列化的形式,示例代码如

下图所示

public class DeepProtoType implements Serializable, Cloneable {

    public String name; //String类型

    public DeepCloneableTarget deepCloneableTarget; //引用类型

    public DeepProtoType() {
        super();
    }

    //深拷贝 - 方式二:通过对象序列化(推荐的)
    public Object deepClone(){

        //创建流对象
        ByteArrayOutputStream bos = null;

        ObjectOutputStream oos = null;

        ByteArrayInputStream bis = null;

        ObjectInputStream ois = null;

        try {
            //序列化
            bos = new ByteArrayOutputStream();

            oos = new ObjectOutputStream(bos);

            oos.writeObject(this);//将当前对象,以对象流的方式输出(即序列化)
            //输出的时候会把包括引用类型一起输出

            //反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());//把输出去的对象再读进来

            ois = new ObjectInputStream(bis);

            DeepProtoType clone = (DeepProtoType)ois.readObject();
            //充分利用序列化和反序列化的特点,把当前对象this以对象流的方式输出去,然后以对象的方式再读回来,关联的引用类型自然也读回来了

            return clone;

        } catch (Exception e) {

            e.getMessage();

        } finally {

            //关闭流操作
            try {
                bos.close();

                oos.close();

                bis.close();

                ois.close();

            } catch (IOException e) {

                e.getMessage();

            }
        }

        return null;
    }

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

public class DeepCloneableTarget implements Serializable, Cloneable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    private String cloneName;

    private String cloneClass;

    public DeepCloneableTarget(String cloneName, String cloneClass) {
        this.cloneName = cloneName;
        this.cloneClass = cloneClass;
    }

    //因为该类的属性都是String,所以我们使用默认的clone方法完成即可
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "DeepCloneableTarget{" +
                "cloneName='" + cloneName + '\'' +
                ", cloneClass='" + cloneClass + '\'' +
                '}';
    }
}

// 测试 方式二 通过对象序列化
public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        DeepProtoType deepProtoType = new DeepProtoType();

        deepProtoType.name = "小李";

        deepProtoType.deepCloneableTarget = new DeepCloneableTarget("老李","克隆类");

        DeepProtoType clone = (DeepProtoType)deepProtoType.deepClone();

        DeepProtoType clone1 = (DeepProtoType)deepProtoType.deepClone();

        System.out.println(clone.toString());

        System.out.println(clone1.toString());

        System.out.println(clone.deepCloneableTarget.hashCode());

        System.out.println(clone1.deepCloneableTarget.hashCode()); //测试哈希值不一致
    }
}

可以看到,经测试,哈希值不一样,说明确实整个对象(包括对象的引用类型)都进行了拷贝 ,我们通过对象序列化的方式也实现了深拷贝,而且利用序列化和反序列化的特点,把当前对象this以对象流的方式输出去,然后以对象的方式再读回来,关联的引用类型的属性值自然也读回来了,这种方式是推荐大家使用的因为是直接把当前整个对象进行序列化和反序列化操作,那不管类的结构如何复杂,都可以通过对象序列化的方式整体处理

注意事项

原型模式到这里就告一段落了,下面我们一起来总结下原型模式的注意事项,分析下优劣势以及应用场景

优势

创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也可以提高效率

不用重新初始化对象,而是动态地获得对象运行时的状态

如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码

劣势

需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,会违背 ocp 原则,这点需要和小伙伴们提一下

下节预告

OK, 下一节我们进入下一个模式 - 建造者模式的学习,希望大家在学习的过程中,能够感觉到设计模式的有趣之处,高效而愉快的学习,那我们下期见~

相关文章
|
3月前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
【Java笔记+踩坑】设计模式——原型模式
|
3月前
|
设计模式 Java
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
Java设计模式-原型模式(3)
|
5月前
|
设计模式
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
iLogtail设计模式问题之iLogtail中的原型模式是什么
|
5月前
|
设计模式 JavaScript
js设计模式【详解】—— 原型模式
js设计模式【详解】—— 原型模式
52 6
|
6月前
|
设计模式 Java
Java设计模式之原型模式详解
Java设计模式之原型模式详解
|
6月前
|
设计模式
原型模式-大话设计模式
原型模式-大话设计模式
|
6月前
|
设计模式 Java Spring
设计模式——原型模式
设计模式——原型模式
|
6月前
|
设计模式 存储 架构师
设计模式-值类型与引用类型、深拷贝与浅拷贝、原型模式详解
 如果拷贝的时候共享被引用的对象就是浅拷贝,如果被引用的对象也拷贝一份出来就是深拷贝。(深拷贝就是说重新new一个对象,然后把之前的那个对象的属性值在重新赋值给这个用户)
162 0
|
7月前
|
设计模式 测试技术 Go
[设计模式 Go实现] 创建型~ 原型模式
[设计模式 Go实现] 创建型~ 原型模式
|
7月前
|
设计模式 Java Go
[设计模式Java实现附plantuml源码~创建型] 对象的克隆~原型模式
[设计模式Java实现附plantuml源码~创建型] 对象的克隆~原型模式