小谈原型模式 | 深复制与浅复制
一、前言
看了上一期的单例模式之后,是不是可以应对一些简单的面试题目了,我相信你可以的。第一稿的原型模式我感觉是比较好完成了,书籍包括视频上面,都花了很长的篇幅,写上一篇感觉不算费力。这一篇还真是不好写,我先从我觉得较简单的说起吧,这一篇说原型模式,然后说说浅复制和深复制的区别,值类型和引用类型…开讲啦
二、原型模式的运用场景和解释
看了《大话设计模式》和一些博客,他们都喜欢用简历这个例子来说明原型模式。在投简历的时候我们是写好一份简历,然后复制疯狂投简历。当然我们也对简历进行一些简单岗位薪资方面的修改,这个时候不需要修改基本信息,这时就用到了设计模式了。每次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。
四、浅复制和深复制的区别
在本文的原型模式的最后的两个栗子当中,大家应该可以体会到浅复制和深复制的区别。
浅复制:被复制的所有变量都含有原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
深复制:它会把引用对象的变量指向复制的新对象,而不是原有的被引用的对象。
下面引用一下别人的博客,仔细推敲文字还是非常不错的,如果有用,记得给别人点赞。
- 浅拷贝可以使用列表自带的copy()函数(如list.copy()),或者使用copy模块的copy()函数。深拷贝只能使用copy模块的deepcopy(),所以使用前要导入:from copy import deepcopy。
- 如果拷贝的对象里的元素只有值,没有引用,那浅拷贝和深拷贝没有差别,都会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
- 如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象完全分离开并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开。
原博客: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种设计原则一系列课程,其目的就是一个简单的记录学习的过程。不知道能帮助到多少人,也不知道技术是否会有一定的深度。
制作不易,您的点赞是我最大的动力。
希望我们都能成为想成为的人