5. 原型模式(Prototype):浅克隆与深克隆、Cloneable接口、序列化实现深克隆,以及原型管理器与性能优化
原型模式,说白了就是「复制粘贴」的设计模式。你想想看,有时候创建一个对象特别费劲——要查数据库、要算一堆参数、要组装复杂结构。这时候如果有个现成的对象,直接复制一份改改,是不是省事多了?
我个人习惯把原型模式理解为「对象的复印机」。Java里提供了Cloneable接口,C++里也有拷贝构造函数。但这里有个坑——浅克隆和深克隆的区别,很多人栽在这上面。
5.1 浅克隆 vs 深克隆:一个血泪教训
先说说浅克隆。它只复制基本类型字段,引用类型字段只复制引用地址。什么意思呢?就是两个对象共享同一个子对象。
我曾经在一个订单系统里用过浅克隆。当时有个订单模板对象,里面包含一个商品列表。我克隆了一份用来生成新订单,结果修改新订单的商品时,模板的商品也跟着变了。嗯,那天的线上事故,至今记忆犹新。
- 浅克隆:复制对象本身,内部引用对象共享
- 深克隆:复制对象及其所有引用对象,完全独立
5.2 Java中的Cloneable接口
Java里实现原型模式,需要做两件事:实现Cloneable接口,重写clone()方法。注意,Cloneable是个标记接口,不实现它的话调用clone()会抛异常。
// 浅克隆示例
public class Order implements Cloneable {
private String orderId;
private List<String> items; // 引用类型
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 默认浅克隆
}
}
// 使用
Order template = new Order("T001", Arrays.asList("商品A", "商品B"));
Order cloned = (Order) template.clone();
// 注意:cloned.items 和 template.items 是同一个对象!
为什么会这样?因为Object的clone()方法默认做的是逐字段复制。对于基本类型没问题,但对于引用类型,它只复制了引用地址。
5.3 深克隆的两种实现方式
深克隆有两种主流做法:手动深克隆和序列化深克隆。我一般根据场景选择。
方式一:手动深克隆
@Override
protected Object clone() throws CloneNotSupportedException {
Order cloned = (Order) super.clone();
// 手动复制引用对象
cloned.items = new ArrayList<>(this.items);
return cloned;
}
这种方式性能好,但需要你清楚每个引用字段的结构。如果对象嵌套很深,写起来很痛苦。
方式二:序列化实现深克隆
我个人比较喜欢这种方式,尤其是对象结构复杂的时候。原理很简单:把对象写到流里,再读回来。这样所有引用对象都会被完整复制。
// 需要实现Serializable接口
public class Order implements Serializable {
private String orderId;
private List<String> items;
public Order deepClone() {
try {
// 写入字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 从字节流读取
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (Order) ois.readObject();
} catch (Exception e) {
throw new RuntimeException("深克隆失败", e);
}
}
}
5.4 原型管理器与性能优化
原型管理器,说白了就是个对象缓存池。把常用的原型对象注册进去,需要的时候直接克隆,不用每次都从头创建。
public class PrototypeManager {
private static Map<String, Prototype> prototypes = new ConcurrentHashMap<>();
public static void register(String key, Prototype prototype) {
prototypes.put(key, prototype);
}
public static Prototype getClone(String key) {
Prototype prototype = prototypes.get(key);
return prototype != null ? prototype.clone() : null;
}
}
// 使用
PrototypeManager.register("orderTemplate", new Order("模板", ...));
Order order = (Order) PrototypeManager.getClone("orderTemplate");
性能优化方面,有几点值得注意:
- 缓存原型对象:避免重复创建开销大的对象
- 懒加载:原型对象可以按需创建并注册
- 对象池:对于频繁克隆的场景,可以复用克隆后的对象
- 避免循环引用:序列化深克隆时,循环引用会导致栈溢出
| 方式 | 性能 | 适用场景 |
|---|---|---|
| 浅克隆 | 极高 | 对象只有基本类型字段 |
| 手动深克隆 | 高 | 对象结构简单且明确 |
| 序列化深克隆 | 低 | 对象结构复杂,克隆频率低 |
最后说个实际案例。我之前做一个报表系统,每个报表模板包含几十个配置项、数据源、样式定义。如果每次都从数据库加载再组装,响应时间要2秒多。后来用原型模式,把常用模板注册到管理器里,克隆一个只要几毫秒。嗯,性能提升就是这么明显。