1.5 原型模式
原型模式(Prototype)用一种和工厂完全不同的思路创建对象:不是从零构造,而是复制一个已有的对象。 当一个对象的初始化代价很高(要读配置、查数据库、做复杂计算),但你需要很多“几乎一样”的副本时,克隆往往比重新构造快得多,也简单得多。
它还有一个隐藏价值:当你拿到的是一个对象实例、却不知道它的具体类时,调用它的 clone() 依然能得到同类型的新对象。
但原型有一个几乎人人踩过的坑:浅拷贝 vs 深拷贝。下面的复制档案实验只做一件事:复制一份档案,然后改副本,看看原件会不会被带着改。
正在加载交互实验...
浅拷贝为什么会“漏”
浅拷贝只复制对象最外层的字段。如果某个字段是引用(指向另一个可变对象),那么克隆体和原件会共享同一个内部对象:
java
class Doc implements Cloneable {
Address address; // 引用类型
// 浅拷贝:address 仅复制了引用,仍然共享
public Doc clone() throws CloneNotSupportedException {
return (Doc) super.clone();
}
// 深拷贝:连内部对象也复制一份,彻底隔离
public Doc deepClone() throws CloneNotSupportedException {
Doc copy = (Doc) super.clone();
copy.address = this.address.clone();
return copy;
}
}改克隆体的 address.city,原件的 city 也跟着变——这就是浅拷贝的“漏”。在上面的实验里打开“深拷贝”开关,就能看到两个对象彻底独立。
注意
实战提示:在 Java 里很多人嫌
Cloneable 设计得别扭,转而用拷贝构造函数、序列化反序列化,或库函数(如 BeanUtils、JSON 往返)来做深拷贝。模式的精神不变:通过复制现有实例来创建新对象。正在加载概念检查...
原型注册表:批量克隆
原型常和一个“注册表”搭配:把若干配置好的原型登记起来,需要时按 key 取出并克隆。这样既避免重复的复杂初始化,又能让每个副本相互独立。
下面的原型注册表战场:点击敌人类型就等于 registry.get(kind).clone(),克隆出独立的实例;单独削减某个克隆体的血量,其它克隆体毫发无伤。
正在加载交互实验...
正在加载概念检查...
什么时候用原型
- 对象创建成本高(重初始化),但你需要很多相似副本。
- 你想在运行时按“当前对象的状态”复制,而不是回到初始配置。
- 你希望屏蔽具体类,仅通过
clone()复制手上的实例。
反之,如果对象很轻、构造很便宜,直接 new 更清楚——而且别忘了深浅拷贝带来的额外心智负担。
正在加载本节练习...