设计模式----原型模式与深浅拷贝
/** * @author 陈柏宇 * 原型模式 * 介绍 : 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象 * * 举个例子,我们定义了一个类,类里面有具体的信息 * 如果客户端有新建 20 个相同对象的需求,那么我们岂不是要实例化 20 个对象 * 而且如果一个对象原型的数据出现了差错,那我们岂不是还要修该刚刚实例化的20个对象 * 这样效率是不是太低了一点 * * 于是在这个基础上提出了 原型模式 (Prototype) * 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不用知道任何创建的细节 */
首先来看一下UML类图
java这里的原型模式的实现和c#的原型模式有点不同,java这里需要实现一个Clonable接口并且重写它里面的方法。
代码如下:
public class Prototype implements Cloneable{ private String id ; public Prototype(String id) { this.id = id; } public String getId() { return id; } /* clone() : Cloneable接口中的方法 创建一个新的对象,然后将当前对象的非静态字段复制到新对象 如果字段是值类型的,则对该字段执行逐位赋值。如果字段是引用类型的,则复制引用而不复制引用的对象 因此,原始对象及其副本引用同一对象,所以要注意深浅拷贝的问题,如果想实现深拷贝 应该让引用也实现 Cloneable接口 */ @Override public Prototype clone() throws CloneNotSupportedException { return (Prototype)super.clone(); } } class ConcretePrototype extends Prototype { public ConcretePrototype(String id) { super(id); } @Override public Prototype clone() throws CloneNotSupportedException { return super.clone(); } }
main函数中的代码:
public static void main(String[] args) { ConcretePrototype p1 = new ConcretePrototype("195030103"); //第一份对象创建(原型对象) ConcretePrototype p2 = null; //第二份对象(复制的第一份对象) try { p2 = (ConcretePrototype) p1.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } System.out.println(p1.getId() == p2.getId()); //两个对象的唯一标识符 id是否相同 System.out.println(p1 == p2); //两个引用指向的地址是否相同 }
输出台:
true //这说明两个对象中的关键属性id相同 false //两个对象引用不同,那么在堆中就有两个实例,符合要求。
现在我们来看一个具体的例子:复印简历。
简历(Resume)类:
public class Resume implements Cloneable{ private String name; private String sex; private int age; private String timeArea; private String company; public Resume(String name) { this.name = name; } //设置个人信息 public void setPersonalInfo(String sex,int age) { this.sex = sex; this.age = age; } //设置工作经历 public void setWorkExperience(String timeArea,String company) { this.timeArea = timeArea; this.company = company; } //显示 public void display() { System.out.println(name + " " + age + " " + sex); System.out.println("工作经历 " + timeArea + " " + company ); } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
客户端调用代码:
public static void main(String[] args) { Resume a = new Resume("小明"); a.setPersonalInfo("男",30); a.setWorkExperience("2018-2020","阿里巴巴"); Resume b = null; try { b = (Resume) a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } b.setWorkExperience("2014-2019","字节跳动"); Resume c = null; try { c = (Resume) a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } c.setPersonalInfo("女",35); a.display(); b.display(); c.display(); }
结果显示:
小明 30 男 工作经历 2018-2020 阿里巴巴 小明 30 男 工作经历 2014-2019 字节跳动 小明 35 女 工作经历 2018-2020 阿里巴巴
使用原型模式可以让我们减少构造函数的调用,如果构造函数的执行时间很长,那么多次的执行初始化操作就太低效了。
一般在初始化的信息不发生变化的情况下,克隆是最好的办法,这即隐藏了对象的创建细节,也是对性能的大大提高。
它等于是不用重新初始化对象,而是动态地获得对象运行时的状态。
事情还没有结束
clone()方法是这样的,如果字段是值类型的,那么就对该字段逐位复制,如果是引用类型,那么赋值引用但不赋值引用的对象,
因此,原始对象及其副本引用同一个对象。
这就是深拷贝和浅拷贝的问题了。
这里我把我之前的代码修改一下
在简历(Resume)类中新增一个工作经验类的成员对象。
/** * @author 陈柏宇 * 工作经验类 Resume类的成员之一 */ 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; } }
Resume类的修改:
public class Resume implements Cloneable{ private String name; private String sex; private int age; private WorkExperience workExperience; //新增一个对象成员 public Resume(String name) { this.name = name; workExperience = new WorkExperience(); } //设置个人信息 public void setPersonalInfo(String sex,int age) { this.sex = sex; this.age = age; } //设置工作经历 public void setWorkExperience(String timeArea,String company) { this.workExperience.setCompany(company); this.workExperience.setTimeArea(timeArea); } //显示 public void display() { System.out.println(name + " " + age + " " + sex); System.out.println("工作经历 " + this.workExperience.getTimeArea() + " " + this.workExperience.getCompany() ); } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
这时候我不改我的原来客户端的代码,再跑一遍
public static void main(String[] args) { Resume a = new Resume("小明"); a.setPersonalInfo("男",30); a.setWorkExperience("2018-2020","阿里巴巴"); Resume b = null; try { b = (Resume) a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } b.setWorkExperience("2014-2019","字节跳动"); Resume c = null; try { c = (Resume) a.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } c.setPersonalInfo("女",35); a.display(); b.display(); c.display(); }
输出:
小明 30 男 工作经历 2014-2019 字节跳动 小明 30 男 工作经历 2014-2019 字节跳动 小明 35 女 工作经历 2014-2019 字节跳动
这个时候发现出问题了,我们只是第二份简历做了修改,把阿里巴巴换成了字节跳动,但是结果却发现三份简历的工作经验都变了
这就是出了深拷贝与浅拷贝的问题了
这个原因叫做浅拷贝,被复制的对象的成员对象(引用) 都指向原来的对象。
但我们的需求不同呀,我们需求是a,b,c三个对象都是不同的,复制就是一变二,二变三,这时候就要用到深拷贝了。
深拷贝把引用对象的变量指向复制过的新对象,而不是原来的被引用的对象。
那么如何实现深拷贝呢?
也很简单,让对象成员所在的类也实现Cloneable类,重写clone方法。
改写代码:
在WorkExprience类中改写clone方法
@Override protected Object clone() throws CloneNotSupportedException { return (Object) super.clone(); }
在Resume类中新增一个构造器
private Resume(WorkExperience workExperience) { try { this.workExperience = (WorkExperience) workExperience.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } }
对clone()方法的改写
public Object clone() throws CloneNotSupportedException { Resume obj = new Resume(this.workExperience); obj.name = this.name; obj.sex = this.sex; obj.age = this.age; return obj; }
可是万一我们的成员对象所在类中也有数据对象那岂不是还要进行深拷贝吗,这样就可以套娃了呀。
没错,是这么回事,这是个很难回答的问题,深复制到底要到多少层,这个问题需要事先就考虑好,而且要小心出现循环引用的问题,
这个问题比较复杂,可以慢慢研究。