小谈原型模式 | 深复制与浅复制

一、前言

看了上一期的单例模式之后,是不是可以应对一些简单的面试题目了,我相信你可以的。第一稿的原型模式我感觉是比较好完成了,书籍包括视频上面,都花了很长的篇幅,写上一篇感觉不算费力。这一篇还真是不好写,我先从我觉得较简单的说起吧,这一篇说原型模式,然后说说浅复制和深复制的区别,值类型和引用类型…开讲啦

二、原型模式的运用场景和解释

看了《大话设计模式》和一些博客,他们都喜欢用简历这个例子来说明原型模式。在投简历的时候我们是写好一份简历,然后复制疯狂投简历。当然我们也对简历进行一些简单岗位薪资方面的修改,这个时候不需要修改基本信息,这时就用到了设计模式了。每次New一个对象,都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次执行这个初始化的操作不就很浪费时间呀。所以在大部分信息不变的情况下,克隆是最好的办法。这即隐藏了对象创建的细节,又对性能是大大的提高了。

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

三、先看看这几个简单的程序

每次新建一份简历我都new一下

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume(String name){
        this.name = name;
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
}

//主函数

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume a = new Resume("大鸟");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume b = new Resume("大鸟");
        b.SetPersonalInfo("男","31");
        b.SetWorkExperience("2019-2020","xx公司");

        Resume c = new Resume("大鸟");
        c.SetPersonalInfo("男","32");
        c.SetWorkExperience("2019-2020","xx公司");

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
	}
}

//OutPut

prototype.Resume@74a14482
prototype.Resume@1540e19d
prototype.Resume@677327b6

我们新建了3份简历,简历有一些不同,选择new了3次,等到了3份简历。

直接引用不复制了(错误)

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume(String name){
        this.name = name;
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
}

//主函数

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume a = new Resume("大鸟");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");
        Resume b = a;
        Resume c = a;

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
	}
}

//OutPut

prototype.Resume@74a14482
prototype.Resume@74a14482
prototype.Resume@74a14482

这是a、b、c都是同一个对象,这其实违背了复制的想法,是错误的。简单理解这是b和c的内容是:简历在a处。

不想复制的时候new对象了

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume2 implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume2(String name){
        this.name = name;
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

//主函数

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume2 a = new Resume2("大鸟");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume2 b = (Resume2) a.clone();
        Resume2 c = (Resume2) a.clone();

        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
    }
}

//OutPut

prototype.Resume2@74a14482
prototype.Resume2@1540e19d
prototype.Resume2@677327b6

可以看到这种clone的方式和new相比起来还是很简便的,只需要一句就行。如果说你需要初始化100个对象,会花上很多的时间,那么这一句是很好的选择。

对副本进行简单的修改

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume2 implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    public Resume2(String name){
        this.name = name;
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.timeArea = timeArea;
        this.company = company;
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Resume2{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", timeArea='" + timeArea + '\'' +
                ", company='" + company + '\'' +
                '}';
    }
}

//主函数

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume2 a = new Resume2("大鸟");
        a.SetPersonalInfo("男","30");
        a.SetWorkExperience("2019-2020","xx公司");

        Resume2 b = (Resume2) a.clone();
        b.SetWorkExperience("2018-2019","yy公司");
        Resume2 c = (Resume2) a.clone();
        c.SetWorkExperience("2018-2019","zz公司");
        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume2{name='大鸟', sex='男', age='30', timeArea='2019-2020', company='xx公司'}
b= Resume2{name='大鸟', sex='男', age='30', timeArea='2018-2019', company='yy公司'}
c= Resume2{name='大鸟', sex='男', age='30', timeArea='2018-2019', company='zz公司'}

克隆之后,我们可以对简历进行下一步的修改,复制也想做自己。

引用类型就不能做自己么

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume3 implements Cloneable{
    private String name;
    private String sex;
    private String age;

    private WorkExperience workExperience;

    public Resume3(String name){
        this.name = name;
        this.workExperience = new WorkExperience();
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.workExperience.setCompany(company);
        this.workExperience.setTimeArea(timeArea);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Resume3{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", workExperience=" + workExperience.getCompany() +
                ",  " + workExperience.getTimeArea() +
                '}';
    }
}

//工作经历类

package prototype;

public class WorkExperience {
    private String timeArea;
    private String company;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }
}

//主函数


package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume3 a = new Resume3("大鸟");
        a.SetWorkExperience("2019-2020","xx公司");
        a.SetPersonalInfo("男","30");
        
        Resume3 b = (Resume3) a.clone();
        b.SetWorkExperience("2019-2020","yy公司");
        b.SetPersonalInfo("男","31");
        
        Resume3 c = (Resume3) a.clone();
        c.SetWorkExperience("2019-2020","zz公司");
        c.SetPersonalInfo("男","32");
        
        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume3{name='大鸟', sex='男', age='30', workExperience=zz公司,  2019-2020}
b= Resume3{name='大鸟', sex='男', age='31', workExperience=zz公司,  2019-2020}
c= Resume3{name='大鸟', sex='男', age='32', workExperience=zz公司,  2019-2020}

我们本来是想对3个不同的对象分别个性化他们的workExperience,但是好像程序并不尽人意。这就需要引出一个知识点:浅复制和深复制
浅复制:被复制的所有变量都含有原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深复制:把引用对象的变量指向复制的新对象,而不是原有的被引用的对象。

还有另一个知识点那就是:值类型和引用类型。当我们clone的时候,创建当前对象的浅表副本,方法是创建一个新的对象,然后将当前对象的非静态字段复制到该新对象。

如果字段是值类型,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不会复制引用的对象

引用类型的变化

//简历类

package prototype;

/**
 * 原型模式:用原型实例指定创建对象的总类,并且通过拷贝这些原型创建新的对象
 * */

public class Resume4 implements Cloneable{
    private String name;
    private String sex;
    private String age;

    private WorkExperienceImprove workExperience;

    public Resume4(String name){
        this.name = name;
        this.workExperience = new WorkExperienceImprove();
    }
    public Resume4(WorkExperienceImprove workExperience) throws CloneNotSupportedException {
        this.workExperience = (WorkExperienceImprove) workExperience.clone();
    }
    //设置个人信息
    public void SetPersonalInfo(String sex,String age){
        this.sex = sex;
        this.age = age;
    }
    //设置工作经历
    public void SetWorkExperience(String timeArea,String company){
        this.workExperience.setCompany(company);
        this.workExperience.setTimeArea(timeArea);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Resume4 resume4 = new Resume4(this.workExperience);
        resume4.name = this.name;
        resume4.age = this.age;
        resume4.sex = this.sex;
        return resume4;
    }

    @Override
    public String toString() {
        return "Resume3{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age='" + age + '\'' +
                ", workExperience=" + workExperience.getCompany() +
                ",  " + workExperience.getTimeArea() +
                '}';
    }
}

//工作经历类

package prototype;

public class WorkExperienceImprove implements Cloneable {
    private String timeArea;
    private String company;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

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

//主函数

package prototype;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Resume4 a = new Resume4("大鸟");
        a.SetWorkExperience("2019-2020","xx公司");
        a.SetPersonalInfo("男","30");

        Resume4 b = (Resume4) a.clone();
        b.SetWorkExperience("2019-2020","yy公司");
        b.SetPersonalInfo("男","31");

        Resume4 c = (Resume4) a.clone();
        c.SetWorkExperience("2019-2020","zz公司");
        c.SetPersonalInfo("男","32");

        System.out.println("a= " + a.toString());
        System.out.println("b= " + b.toString());
        System.out.println("c= " + c.toString());
    }
}

//OutPut

a= Resume3{name='大鸟', sex='男', age='30', workExperience=xx公司,  2019-2020}
b= Resume3{name='大鸟', sex='男', age='31', workExperience=yy公司,  2019-2020}
c= Resume3{name='大鸟', sex='男', age='32', workExperience=zz公司,  2019-2020}

这个例子,第一次看,还有点难懂,主要就是我们需要把workExperience也实现Cloneable的clone接口

public Resume4(WorkExperienceImprove workExperience) throws CloneNotSupportedException {
    this.workExperience = (WorkExperienceImprove) workExperience.clone(); 
}
@Override
protected Object clone() throws CloneNotSupportedException {
    Resume4 resume4 = new Resume4(this.workExperience);
    resume4.name = this.name;
    resume4.age = this.age;
    resume4.sex = this.sex;
    return resume4;
}

然后通过重写clone方法,传入workExperience的对象,通过构造方法克隆workExperience。

四、浅复制和深复制的区别

在本文的原型模式的最后的两个栗子当中,大家应该可以体会到浅复制和深复制的区别。
浅复制:被复制的所有变量都含有原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象
深复制:它会把引用对象的变量指向复制的新对象,而不是原有的被引用的对象。

下面引用一下别人的博客,仔细推敲文字还是非常不错的,如果有用,记得给别人点赞

  1. 浅拷贝可以使用列表自带的copy()函数(如list.copy()),或者使用copy模块的copy()函数。深拷贝只能使用copy模块的deepcopy(),所以使用前要导入:from copy import deepcopy。
  2. 如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
  3. 如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。

原博客:https://blog.csdn.net/qq_34493908/article/details/81560546

五、值类型和引用类型的区别

在本文的栗子中,当我们clone的时候,创建当前对象的浅表副本,方法是创建一个新的对象,然后将当前对象的非静态字段复制到该新对象。如果字段是值类型,则对该字段执行逐位复制。如果字段是引用类型,则复制引用但不会复制引用的对象

网上找了一些资料,关于值类型和引用类型,他们举例子都是使用的C#,我也用C#吧。
C#把数据类型分为两大类:值类型(数据存放在栈中)和引用类型(数据存放在堆中,但是地址存放在栈中)。值类型包含:简单类型、枚举和结构体。引用类型包含:数组,字符串,接口,类,委托。完了!越解释越乱了,要不我们直接看上面写的第二个例子吧,Resume b = a;Resume c = a;这就是引用类型,把b和c指向a的地址,其他这时a,b,c都是一样的。

六、参考资料

书籍:《大话设计模式》 第9章 简历复印–原型模式
博客:https://blog.csdn.net/qq_34493908/article/details/81560546

七、关于本系列的解释

本系列想制作23种设计模式+7种设计原则一系列课程,其目的就是一个简单的记录学习的过程。不知道能帮助到多少人,也不知道技术是否会有一定的深度。

制作不易,您的点赞是我最大的动力。

希望我们都能成为想成为的人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小胖丨学编程

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值