原型模型
1. 原型模式介绍
1.1 出现原因——克隆羊问题
- 构建新对象时,需要重复获取原始对象的属性
- 总是需要重新初始化一个新的对象,而不是动态地获取对象运行时的状态
解决思路是利用Object的clone()方法以及Cloneable接口来实现类的克隆功能
1.2 原型模式
原型模式 (Prototype模式)是指:通过已有的原型实例,克隆出新的同一对象
主要解决问题:在运行期建立和删除原型。原型模式是一种创建设计模式,允许一个对象再创建另外一个可定制的对象,但无需知道创建的细节。此外,克隆还可以逃避构造函数的约束
工作原理:Cloneable接口以及Object的clone()方法
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有环结构的时候。 2、必须实现 Cloneable 接口。
1.3 使用
何时使用:
- 当一个系统应该独立于它的产品创建、构成和表示时
- 当要实例化的类是在运行时刻指定时,例如,通过动态装载
- 为了避免创建一个与产品类层次平行的工厂类层次时
- 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
使用场景:
- 与备忘录模式搭配使用
- 资源优化场景
- 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
- 性能和安全要求的场景
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
- 一个对象多个修改者的场景
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用
- 在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
2. 实现
我们将创建一个抽象类 Shape 和扩展了 Shape 类的实体类。下一步是定义类 ShapeCache,该类把 shape 对象存储在一个 Hashtable 中,并在请求的时候返回它们的克隆。
PrototypePatternDemo 类使用 ShapeCache 类来获取已有的 Shape 对象的克隆。
看一下Cloneable怎么实现:
public abstract class Shape implements Cloneable {
private String id;
protected String type;
abstract void draw();
public Object clone() {
Object clone = null;
try {
clone = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return clone;
}
}
3. 浅拷贝与深拷贝
3.1 区别和概念
对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值
深拷贝要求对该对象及对象成员进行递归深拷贝
3.2 深拷贝的实现方式
3.2.1 使用clone方法实现
可以我的另一篇博客:protected访问权限解释——以Object的clone()方法为例,里面也涉及到了克隆
使用clone方法实现深拷贝,需要在类重载的clone()方法中,不仅仅调用super.clone()
方法克隆自身,还要对引用数据类型显示地调用clone()方法
3.2.2 序列化
public Object deepClone() {
// 创建流对象
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
try {
// 序列化
bos = new ByteArrayOutputStream();
// 转载bos
oos = new ObjectOutputStream(bos);
// 序列化当前对象(深拷贝对象),写入到bos中
oos.writeObject(this);
// 反序列化
// 从bos中获取序列的的对象的序列流
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
// 这里也可以强转为拷贝的对象类型
Object copy = ois.readObject();
return copy;
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
// 关闭流
}
}