1、简介
原型模式(Prototype Pattern)用于创建重复的对象,同时又能保证性能。它属于创建型设计模式,它提供了一种创建对象的最佳方法。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。在需要短时间创建大量的对象和创建对象很耗时的情况下,原型模式比通过new对象大大提高了时间效率。
使用场景;
1、资源优化场景。
2、类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等。
性能和安全要求的场景。
3、通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
4、一个对象多个修改者的场景。
5、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
6、在实际项目中,原型模式很少单独出现,一般是和工厂模式一起出现,通过clone方法创建一个对象,然后由工厂方法提供给调用者。
注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现Cloneable,重写,深拷贝是通过实现Serializable读取二进制流。
2、具体实现
class student implements Cloneable{ //继承Cloneable接口 实现拷贝
private String name;
private String sex;
// Getter...Setter...
public student(){
// wait()。。。
//假设一个类的初始化需要很长的时间
}
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
student s = new student();
student s1 = (student) s.clone(); //只是对已有对象的克隆,在堆中进行,不执行类加载过程,不执行类初始化
}
}
假设上述student类的初始化要执行很长时间的话 使用克隆模式是一种很高效的办法。
3、浅拷贝
(1)简介
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。
(2)特点
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个。
(2) 对于引用类型,比如数组或者类对象,因为引用类型是引用传递,所以浅拷贝只是把内存地址赋值给了成员变量,它们指向了同一内存空间。改变其中一个,会对另外一个也产生影响。
class subject{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class student implements Cloneable{ //继承Cloneable接口 实现拷贝
private String name;
private subject subject;
// Getter...Setter...
public student(){
// wait()。。。
//假设一个类的初始化需要很长的时间
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public com.company.subject getSubject() {
return subject;
}
public void setSubject(com.company.subject subject) {
this.subject = subject;
}
protected Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
subject subjectA = new subject();
subjectA.setName("aaa");
student studentA = new student();
studentA.setName("AAA");
studentA.setSubject(subjectA);
// 克隆前
System.out.println("我是A 我的名字是"+studentA.getName()+" 我自己的subject是"+studentA.getSubject().getName());
//另一个
student studentB = (student) studentA.clone();
System.out.println("我是b 我的名字是"+studentB.getName());
subject subjectB = studentB.getSubject();
subjectB.setName("bbbb");
System.out.println("我还是A B在克隆完后 本来改变的是他的subject 但是我现在的subject名字是"+studentA.getSubject().getName());
}
}
运行结果:
由此可见,浅克隆 ,基本数据类型克隆的直接是值,对象克隆的是引用,如果克隆后发生改变,那被克隆之前的对象也会改变。
5、深拷贝
(1)简介
通过上面的例子可以看到,浅拷贝会带来数据安全方面的隐患,例如我们只是想修改了 studentB 的 subject,但是 studentA 的 subject 也被修改了,因为它们都是指向的同一个地址。所以,此种情况下,我们需要用到深拷贝。
深拷贝,在拷贝引用类型成员变量时,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。
(2)特点
(1) 对于基本数据类型的成员对象,因为基础数据类型是值传递的,所以是直接将属性值赋值给新的对象。基础类型的拷贝,其中一个对象修改该值,不会影响另外一个(和浅拷贝一样)。
(2) 对于引用类型,比如数组或者类对象,深拷贝会新建一个对象空间,然后拷贝里面的内容,所以它们指向了不同的内存空间。改变其中一个,不会对另外一个也产生影响。
(3) 对于有多层对象的,每个对象都需要实现 Cloneable 并重写 clone() 方法,进而实现了对象的串行层层拷贝。
(4) 深拷贝相比于浅拷贝速度较慢并且花销较大。
(3) 实现
class subject implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class student implements Cloneable ,Serializable{ //继承Cloneable接口 实现拷贝
private String name;
private subject subject;
// Getter...Setter...
public student(){
// wait()。。。
//假设一个类的初始化需要很长的时间
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public com.company.subject getSubject() {
return subject;
}
public void setSubject(com.company.subject subject) {
this.subject = subject;
}
protected Object clone() {
try {
//将对象写入流中
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(this);
//从流中取出
ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
return objectInputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
subject subjectA = new subject();
subjectA.setName("aaa");
student studentA = new student();
studentA.setName("AAA");
studentA.setSubject(subjectA);
// 克隆前
System.out.println("我是A 我的名字是"+studentA.getName()+" 我自己的subject是"+studentA.getSubject().getName());
//另一个
student studentB = (student) studentA.clone();
System.out.println("我是b 我的名字是"+studentB.getName());
subject subjectB = studentB.getSubject();
subjectB.setName("bbbb");
System.out.println("我还是A B在克隆完后 改变的是他的subject 我现在的subject名字还是"+studentA.getSubject().getName());
}
}
运行结果:
由此可见 studentB对subject的改变并不能影响studentA中的subject。
总而言之,浅拷贝与深拷贝,除了性能外,最大的区别就是,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象,只是复制了一个引用。深拷贝把要复制的对象所引用的对象都复制了一遍,为引用类型的数据成员另辟了一个独立的内存空间,实现真正内容上的拷贝。