原型模式
你做过报表系统吗?有一种常见的优化策略:报表模板只创建一次,然后复制多份给不同用户使用。
// 每次都从数据库加载模板
ReportTemplate template1 = loadTemplateFromDB();
ReportTemplate template2 = loadTemplateFromDB();
ReportTemplate template3 = loadTemplateFromDB();
数据库加载 IO 成本很高,如果能复制已有模板而不需要重复查询,性能会好很多。原型模式就是来解决这个问题的:通过复制已有对象来创建新对象,而不是通过构造函数。
深拷贝 vs 浅拷贝的实际案例
假设有一个订单系统:
public class Order {
private String orderId;
private List<OrderItem> items; // 订单项列表
private Customer customer; // 客户信息
// 构造函数、getter、setter
}
如果用 Object 的 clone() 方法(如果没有正确实现),会发生什么?
Order original = new Order("O001", items, customer);
Order copy = (Order) original.clone();
// 修改原订单的商品
original.getItems().add(new OrderItem());
// 拷贝的订单也变了!❌
System.out.println(copy.getItems().size()); // 多了 1 个
这就是浅拷贝的问题:只复制了引用,没有复制引用指向的实际对象。
Cloneable 接口的问题
Object.clone() 是 Java 提供的原生克隆方法,但它的设计有缺陷:
- 权限问题:
clone() 是 protected 方法,需要强制转型
- 默认实现是浅拷贝:基本类型复制值,引用类型复制引用
- 必须实现
Cloneable 接口:否则抛出 CloneNotSupportedException
- 契约不明确:
Cloneable 接口没有任何方法,它只是一个标记接口
public class Order implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
}
更好的做法是自定义拷贝方法,而不是依赖 clone()。
深拷贝实现方式
方式一:序列化拷贝
public class Order implements Serializable {
private String orderId;
private List<OrderItem> items;
private Customer customer;
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 (IOException | ClassNotFoundException e) {
throw new RuntimeException("Deep clone failed", e);
}
}
}
优点:实现简单,递归复制所有引用类型
缺点:性能开销大(序列化/反序列化);所有类都要实现 Serializable
方式二:递归拷贝
public class Order {
private String orderId;
private List<OrderItem> items;
private Customer customer;
public Order deepClone() {
Order copy = new Order();
copy.orderId = this.orderId;
copy.items = new ArrayList<>();
for (OrderItem item : this.items) {
copy.items.add(item.clone());
}
copy.customer = this.customer.clone();
return copy;
}
}
public class OrderItem implements Cloneable {
private String productId;
private int quantity;
@Override
public OrderItem clone() {
OrderItem copy = new OrderItem();
copy.productId = this.productId;
copy.quantity = this.quantity;
return copy;
}
}
优点:性能好;类型安全
缺点:每个类都要实现克隆逻辑;如果引用类型变化,克隆逻辑需要同步更新
方式三:JSON 序列化(第三方库)
import com.fasterxml.jackson.databind.ObjectMapper;
public class Order {
private String orderId;
private List<OrderItem> items;
private Customer customer;
public Order deepClone() {
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(mapper.writeValueAsString(this), Order.class);
}
}
优点:实现简单;处理循环引用;JSON 可读性好
缺点:需要第三方库;性能比直接递归拷贝差
原型管理器
当原型对象较多时,可以使用原型管理器统一管理:
public class PrototypeRegistry<T> {
private final Map<String, T> prototypes = new HashMap<>();
public void addPrototype(String key, T prototype) {
prototypes.put(key, prototype);
}
public T getPrototype(String key) {
T prototype = prototypes.get(key);
if (prototype == null) {
throw new IllegalArgumentException("Prototype not found: " + key);
}
try {
return (T) prototype.getClass().getMethod("clone").invoke(prototype);
} catch (Exception e) {
throw new RuntimeException("Clone failed", e);
}
}
public void remove(String key) {
prototypes.remove(key);
}
}
// 使用
PrototypeRegistry<ReportTemplate> registry = new PrototypeRegistry<>();
registry.addPrototype("sales", new SalesReportTemplate());
registry.addPrototype("inventory", new InventoryReportTemplate());
// 复制模板
ReportTemplate salesReport = registry.getPrototype("sales");
原型模式 vs 工厂模式
典型应用场景
1. 性能优化
// 不推荐:每次都查询数据库
Report createReport(ReportTemplate template) {
ReportTemplate t = loadTemplateFromDB(); // 慢
return buildReport(t);
}
// 推荐:使用原型复制
Report createReport(ReportTemplate template) {
ReportTemplate t = template.clone(); // 快
return buildReport(t);
}
2. 配置复制
// 游戏开发中的怪物模板
MonsterTemplate goblinTemplate = loadMonsterTemplate("Goblin");
Monster goblin1 = goblinTemplate.clone();
Monster goblin2 = goblinTemplate.clone();
3. 撤销/重做功能
// 保存当前状态快照
DocumentSnapshot snapshot = currentDocument.clone();
history.push(snapshot);
// 恢复
DocumentSnapshot previous = history.pop();
currentDocument = previous.clone();
反模式警示
1. 滥用浅拷贝
// 错误:没有意识到是浅拷贝
Order copy = (Order) original.clone();
copy.getItems().clear(); // 原订单也清空了!
2. 深拷贝中的循环引用
public class Employee {
private String name;
private Employee manager; // 循环引用!
@Override
public Employee clone() {
Employee copy = new Employee();
copy.name = this.name;
copy.manager = this.manager.clone(); // 无限递归!
return copy;
}
}
需要使用「记录已拷贝对象」的技术来处理循环引用。
思考题
问题 1:为什么说 Cloneable 接口是一个「坏设计」?
参考答案
Cloneable 接口的设计问题:
-
标记接口没有方法:Cloneable 本身是空的,只是标记「此类允许克隆」。这违反了接口的设计原则——接口应该定义行为,而不是权限。
-
clone() 在 Object 中定义:clone() 是 protected 方法,返回值是 Object。调用方需要强制转型,且无法以类型安全的方式使用。
-
默认实现是危险的:默认的 super.clone() 是浅拷贝,容易引发 bug。但没人会主动去重写它。
-
clone() 契约复杂:Object.clone() 的规范要求很多:长度必须相同、所有元素相等、返回的对象应该是正确的类型……但 Cloneable 接口无法强制这些要求。
更好的设计应该是显式定义克隆方法,如 copy() 或 deepCopy(),而不是依赖一个标记接口。
问题 2:深拷贝中如何处理循环引用?
参考答案
以 Employee 部门树为例:
public class Employee {
private String name;
private Employee manager;
private List<Employee> subordinates;
public Employee deepClone() {
Map<Employee, Employee> cloneMap = new IdentityHashMap<>();
return deepClone(cloneMap);
}
private Employee deepClone(Map<Employee, Employee> cloneMap) {
if (cloneMap.containsKey(this)) {
return cloneMap.get(this);
}
Employee copy = new Employee();
copy.name = this.name;
cloneMap.put(this, copy); // 先放入,防止循环引用
copy.manager = this.manager == null ? null : this.manager.deepClone(cloneMap);
copy.subordinates = new ArrayList<>();
for (Employee sub : this.subordinates) {
copy.subordinates.add(sub.deepClone(cloneMap));
}
return copy;
}
}
核心思想:使用 IdentityHashMap 记录「已拷贝的对象」,遇到循环引用时直接返回已有的拷贝,而不是创建新对象。
问题 3:原型模式在 Spring 中有应用吗?
参考答案
Spring 的 @Scope("prototype") 就是原型模式的应用。
@Component
@Scope("prototype")
public class MyBean {
// 每次注入都创建新实例
}
Spring 默认是单例模式(所有 Bean 只创建一次),对于需要每次注入新实例的 Bean,使用 prototype 作用域。
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public OrderProcessor orderProcessor() {
return new OrderProcessor();
}
}
注意:Spring 不会管理原型 Bean 的完整生命周期。销毁回调等不会执行,需要手动处理。