模式介绍
用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
模式图解
原理类图
类图说明
原型模式包含如下角色:
- 抽象原型类:规定了具体原型对象必须实现的
clone()方法。对应类图中的Prototype接口。 - 具体原型类:实现抽象原型类的
clone()方法,它是可被复制的对象。对应类图中的Realizetype类。 - 访问类:使用具体原型类中的
clone()方法来复制新的对象。对应类中的PrototypeTest类。
两种实现方式
浅拷贝
1、对于数据类型是
基本类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
2、对于数据类型是引用数据类型的成员变量,比如数组、对象等,那么浅拷贝会进行引用传递,也就只是将该成员变量的引用值(内存地址)复制一份给新的对象,因为实际上两个对象的该成员变量都指向同一个实例,在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
浅拷贝可以使用默认的clone()方法来是实现:
XXX xxx = (XXX) super.clone();
深拷贝
复制对象的所有基本数据类型的成员变量值;
为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深拷贝要对整个对象进行拷贝。
深拷贝实现方式:
- 重写clone方法来实现深拷贝
- 通过对象序列化实现深拷贝
【推荐】
深拷贝之重写 clone
package model;
import java.io.*;
import java.util.Objects;
/**
* 原型对象
*
* @author asyyr
*/
public class Sheep implements Cloneable, Serializable {
private static final long serialVersionUID = 460754667710L;
private String name;
private int age;
private String color;
private Grass grass;
public Sheep(String name, int age, String color, Grass grass) {
this.name = name;
this.age = age;
this.color = color;
this.grass = grass;
}
@Override
protected Sheep clone() {
Sheep sheep = null;
try {
sheep = (Sheep) super.clone();
sheep.grass = grass.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return sheep;
}
... get/set/toString
}
package model;
import java.io.Serializable;
/**
* 原型对象
*
* @author asyyr
*/
public class Grass implements Cloneable, Serializable {
private static final long serialVersionUID = 470754667710L;
private String name;
private String color;
public Grass(String name, String color) {
this.name = name;
this.color = color;
}
@Override
protected Grass clone() {
Grass grass = null;
try {
grass = (Grass) super.clone();
} catch (CloneNotSupportedException e) {
System.out.println(e.getMessage());
}
return grass;
}
... get/set/toString
}
深拷贝之对象序列化
package model;
import java.io.*;
import java.util.Objects;
/**
* 原型对象
*
* @author asyyr
*/
public class Sheep implements Serializable {
private static final long serialVersionUID = 460754667710L;
private String name;
private int age;
private String color;
private Grass grass;
public Sheep(String name, int age, String color, Grass grass) {
this.name = name;
this.age = age;
this.color = color;
this.grass = grass;
}
protected Sheep deepClone() throws IOException {
Sheep sheep = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
sheep = (Sheep) ois.readObject();
} catch (ClassNotFoundException e) {
System.out.println("deepClone exception:" + e.getMessage());
return sheep;
} finally {
if (Objects.nonNull(ois)) {
ois.close();
}
if (Objects.nonNull(bis)) {
bis.close();
}
}
return sheep;
}
... get/set/toString
}
实现原理
通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝它们自己来实施创建,即对象.clone()。
优点
- 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率;
- 不用重新初始化对象,而是动态地获得对象运行时的状态;
- 如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码;
缺点
- 在实现深克隆的时候可能需要比较复杂的代码;
- 需要为每一个类配备一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了
OCP原则,特别是当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
不可串行化的类
一般而言,满足下面的四个条件之一的类就不应当串行化:
- 一个类与本地代码(native code)有紧密的关系。比如 java.util.zip.Deflater。
- 对象的内部状态依赖于 Java 虚拟机或者运行环境,从而在每一次运行时这个状态都有可能不同。比如 java.lang.Thread,java.io.InputStream,java.io.FileDescriptor,java.awt.PrintJob 等。
- 串行化可能带来潜在的安全隐患。比如,java.lang.SecurityManager 以及 java.security.MessgeDigest 等。
- 一个类仅仅是一些静态方法的存放地,并没有任何的内部状态。比如 java.beans.Beans 和 java.lang.Math 等。
注意事项
- 有些对象,比如 Thread 对象或者 Socket 对象,是不能简单复制或者共享的,不管是使用深复制还是浅复制,只要涉及这样的间接对象,就必须把间接对象设置为 transient 而不予复制;