原型模式的基本概念
- 原型模式(protoType) :指用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象
- 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可制定的对象,无需知道如何创建的细节
- 原理: 通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝他们自己来实施创建,即对象.clone()
浅拷贝和深拷贝
浅拷贝: 对于基本数据类型的变量,浅拷贝直接进行值传递,而对于引用数据类型变量,浅拷贝会进行应用传递,即将该成员变量的引用值(内存地址)赋值给克隆对象,实际上原型对象和克隆对象中该变量都指向同一个实例。这种情况下,就可能出现修改原型对象中成员变量的值,引起克隆对象中的值也发生变化。
深拷贝
复制对象的所有基本数据类型的值,旗下的引用数据类型则也进行拷贝。
深拷贝的实现方式:
- 对原型中成员变量也使用clone方法实现深拷贝 (如果该原型组成较复杂,关联关系较多,实现起来较为复杂)
- 通过对象的序列化实现深拷贝 (无需考虑嵌套关联)
在Java中,原型模式的实现其实就是对克隆方法的调用
实现步骤: 实现Cloneable接口,重新Object的clone()方法
背景:
在软件开发过程中,我们可能再初始化一个对象之后,在某些场景下,可能会重新需要该对象中的大部分内容,只对部分内容进行微调,在这种情况下,我们可以重新在重新初始化对象出来,但是如果该对象的初始化较复杂,会造成效率的低下,并且该方式不是动态的获得对象运行中的状态,不够灵活
而使用原型模式可以解决这种问题。
引子
在找工作的时候我们经常需要写上一篇优美的简历,入职之后呢,可能会将简历保存下来,当在跳槽的时候在将上一份简历找出来,将简历在进行一些修改在用来找工作,而我们用的简历不就可以理解为一份原型,每次找工作的时候我们都是拷贝之前的简历进行微调,接下来让我们用代码来实现。
浅拷贝
简历类
//简历类(原型类)
public class Resume implements Cloneable, Serializable {
private Integer age;
private String name;
//工作经历
private WorkExperience workExperience;
public Resume(Integer age, String name, WorkExperience workExperience) {
this.age = age;
this.name = name;
this.workExperience = workExperience;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = null;
try {
obj = super.clone();
}catch (CloneNotSupportedExceptione) {
System.err.println("Resume is not Cloneable");
}
return obj;
}
public WorkExperience getWorkExperience() {
return workExperience;
}
}
工作经历类
public class WorkExperience {
private Integer workYear;
private String preName;
public WorkExperience(Integer workYear, String preName) {
this.workYear = workYear;
this.preName = preName;
}
}
测试Test
public static void main(String[] args) throws CloneNotSupportedException {
//原型模式
Resume r0 = new Resume(18, "小明", new WorkExperience(3, "XXXXX"));
Resume r1 =(Resume) r0.clone();
System.out.println(r0);
System.out.println(r1);
}
运行结果如下
我们通过原型模式将简历对象重新创建一份出来,发现克隆对象和原型对象的内存地址确实不一样,这时候有个问题,简历类中引用的有工作经历,那么经过简历实例的克隆,克隆对象的工作经历和原型对象的工作经历是否是同一对象呢?
public static void main(String[] args) throws CloneNotSupportedException {
//原型模式
Resume r0 = new Resume(18, "小明", new WorkExperience(3, "XXXXX"));
Resume r1 =(Resume) r0.clone();
System.out.println(r0.getWorkExperience());
System.out.println(r1.getWorkExperience());
}
运行结果如下
我们发现简历类的克隆并没有对成员变量进行再度克隆创建,只是将引用地址指向了克隆对象,这样的克隆会出现问题,我们修改了原型对象的工作经历之后,克隆对象的工作经历也会发生变化。所有我们需要进行更加深度的拷贝。
深拷贝
深拷贝之成员变量的clone()
简历类
public class Resume implements Cloneable, Serializable {
private Integer age;
private String name;
private WorkExperience workExperience;
public Resume(Integer age, String name, WorkExperience workExperience) {
this.age = age;
this.name = name;
this.workExperience = workExperience;
}
/**
* 这种深拷贝方式只适合与拷贝层数比较浅的,并且这种深拷贝可能会修改到其他的实现类,违背了OCP原则,如果想要实现归约拷贝需要另外实现
* @return
*/
@Override
protected Object clone() {
Resume obj = null;
try {
//在进行自身对象的克隆之后
obj = (Resume) super.clone();
//将克隆对象中的成员变量同样克隆一份赋值给自身
obj.workExperience = (WorkExperience)obj.getWorkExperience().clone();
} catch (CloneNotSupportedException e) {
System.err.println("Resume is not Cloneable");
} catch (Exception e) {
System.err.println("Resume is not Cloneable");
}
return obj;
}
public WorkExperience getWorkExperience() {
return workExperience;
}
}
工作经历类
//克隆类也同样去实现Coneable接口,并重新Object的clone方法
public class WorkExperience implements Cloneable, Serializable {
private Integer workYear;
private String preName;
public WorkExperience(Integer workYear, String preName) {
this.workYear = workYear;
this.preName = preName;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
这时我们在进行测试会发现已经将成员变量也进行了深度拷贝
这时候我们需要去思考,如果工作经历下仍然有引用类型的成员变量怎么办,如果它下面的下面还有怎么办?一层套一层,如此复杂的关联关系,我们总不能一个个的去修改这些类。
接下来介绍通过序列化来进行深拷贝
深拷贝之序列化实现
简历类
public class Resume implements Cloneable, Serializable {
private Integer age;
private String name;
private WorkExperience workExperience;
public Resume(Integer age, String name, WorkExperience workExperience) {
this.age = age;
this.name = name;
this.workExperience = workExperience;
}
/**
* 这种将对象写入流中 在重新读取,分配内存空间的方式虽然可以归约拷贝 这种方式是通过对象序列化实现 ,但是耗费大量CPU资源,效率很慢
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = null;
try {
//将对象写入流中
ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(this);
//将对象从流中取出
ByteArrayInputStream bis=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois=new ObjectInputStream(bis);
obj = ois.readObject();
}catch (Exception e) {
e.printStackTrace();
System.err.println("Resume is not Cloneable");
}
return obj;
}
public WorkExperience getWorkExperience() {
return workExperience;
}
}
工作经历类
//工作经历类在这里不在去实现Cloneable接口也不在去重新clone方法
public class WorkExperience {
private Integer workYear;
private String preName;
public WorkExperience(Integer workYear, String preName) {
this.workYear = workYear;
this.preName = preName;
}
}
测试如下
总结
经过本人测试,深拷贝中,通过对旗下成员变量实现克隆来进行深拷贝的方式要比序列化拷贝的方式执行效率要高,但是该方式无法解决层层嵌套的关联关系,并且对以有的代码进行克隆改造,违背了OCP(开放封闭)原则。愿天下所有码农早日秃头