【Java】从零开始学设计模式:原型模式
定义
原型模式
是创建型模式的一种,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的。
适用场景
原型模式多用于创建复杂的或者耗时的实例,因为这种情况下,复制一个已经存在的实例使程序运行更高效;或者创建值相等,只是命名不一样的同类数据
优点
- Java 自带的原型模式基于内存二进制流的复制,在性能上比直接 new 一个对象更加优良。
- 可以使用深克隆方式保存对象的状态,使用原型模式将对象复制一份,并将其状态保存起来,简化了创建对象的过程,以便在需要的时候使用(例如恢复到历史某一状态),可辅助实现撤销操作。
缺点
- 需要为每一个类都配置一个 clone 方法
- clone 方法位于类的内部,当对已有类进行改造的时候,需要修改代码,违背了开闭原则。
- 当实现深克隆时,需要编写较为复杂的代码,而且当对象之间存在多重嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来会比较麻烦。因此,深克隆、浅克隆需要运用得当。
实现
Cloneable 接口
浅复制
几点说明
- Cloneable 是一个空接口,只是一个标记
- 我们重写的clone方法,实际上是重写的 Object 的clone方法
protected native Object clone() throws CloneNotSupportedException;
,而这个方法内部实现又是通过本地方法c/c++
来实现的,所以这也是比new一个对象出来要更快的原因 - clone方法将创建当前对象类的新实例,并使用此对象对应字段的内容初始化其所有字段,就像赋值一样;字段的内容本身不是克隆的。因此,此方法执行此对象的“浅拷贝”,而不是“深拷贝”操作
@Data
public class Wukong implements Cloneable {
private Integer age;
@Override
public Wukong clone() {
try {
return (Wukong) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
深复制
为什么要做到深复制呢?
如果做不到深复制,那么克隆对象在修改完某个引用属性后,被克隆对象的属性也是会被修改的。如下所示,浅复制对象内部的引用属性(非不可变类
:这个后面会说),与被克隆对象内部对应的引用属性,他们指向的同一块堆内存区域。简单一点理解,打个不是特别恰当的比方:
- 浅复制:孙悟空复制出来的小弟,小弟受伤,孙悟空也会跟着受伤;所以这是
危险
⚠️的!!! - 深复制:孙悟空复制出来的小弟,小弟受伤,跟他没丁点关系
public class Immortal implements Cloneable {
@Setter
@Getter
private Wukong wukong;
@Setter
@Getter
private String name;
@Override
public Immortal clone() {
try {
Immortal i = (Immortal) super.clone();
if (this.wukong != null) {
i.setWukong(this.wukong.clone());
}
return i;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
由于Wukong属于非不可变类,所以在Immortal复制成功之后,把被克隆对象内部的Wukong的引用也修改为对应的克隆对象。这里需要判空,不然会有空指针异常。
通过序列化复制
如何利用序列化来完成对象的拷贝呢?在内存中通过字节流的拷贝是比较容易实现的。把被克隆对象写入到一个字节流中,再从字节流中将其读出来,这样就可以创建一个新的对象了,并且该新对象与被克隆对象之间并不存在引用共享的问题,真正实现对象的深拷贝。
@Data
public class Person implements Serializable {
private static final long serialVersionUID = 5947974524451149435L;
private String name;
private Integer age;
public Person copy() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
oos.flush();
ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
bos.close();
oos.close();
return (Person) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
不可变类
参考 Java不可变类