你是否遇到过这样的场景:需要创建大量结构相似、仅少数属性不同的对象(比如 100 个游戏角色,基础属性相同,仅昵称和装备有差异)?如果每次都通过new关键字从头初始化,不仅代码冗余,还会因为重复执行构造函数中的复杂逻辑(比如数据库查询、网络请求)浪费性能。
这时候,原型模式(Prototype Pattern) 就能派上用场 ------ 它的核心思想是:通过复制现有对象(原型)来创建新对象,而非重新初始化,就像现实中 "复制粘贴" 文档再修改细节,既高效又省力。
一、核心概念:克隆,而非新建
原型模式的本质是 "对象克隆",它通过让对象自身提供复制方法,将对象的创建过程从 "主动新建" 转为 "被动复制"。关键角色只有两个:
- 原型接口(Prototype) :定义克隆方法(比如 Java 中的
Cloneable接口,虽然是标记接口,但约定了clone()方法); - 具体原型(Concrete Prototype):实现克隆方法,完成自身复制。
二、实现方式:浅克隆 vs 深克隆
克隆的核心是 "复制对象的属性",但根据属性类型(基本类型 / 引用类型),克隆分为两种:
1. 浅克隆:只复制 "表面"
浅克隆会复制对象的基本类型属性(int、String 等),但对于引用类型属性(比如对象、集合),仅复制引用地址 ------ 新对象和原对象的引用属性会指向同一个内存地址,修改其中一个会影响另一个。
Java 实现示例 (基于Cloneable接口):
java
// 具体原型:游戏角色
class GameRole implements Cloneable {
private String name; // 基本类型
private int level; // 基本类型
private Equipment equipment; // 引用类型(装备)
// 构造函数(模拟复杂初始化逻辑)
public GameRole(String name, int level, Equipment equipment) {
this.name = name;
this.level = level;
this.equipment = equipment;
System.out.println("执行复杂初始化(比如加载角色模型)...");
}
// 实现克隆方法(浅克隆)
@Override
protected GameRole clone() throws CloneNotSupportedException {
return (GameRole) super.clone(); // Object.clone()默认是浅克隆
}
// getter/setter省略
}
// 引用类型:装备
class Equipment {
private String weapon;
public Equipment(String weapon) { this.weapon = weapon; }
// getter/setter省略
}
// 测试浅克隆
public class PrototypeDemo {
public static void main(String[] args) throws CloneNotSupportedException {
// 创建原型对象(执行一次复杂初始化)
Equipment equip = new Equipment("木剑");
GameRole prototype = new GameRole("原型角色", 1, equip);
// 克隆出3个新角色(无需重复初始化)
GameRole role1 = prototype.clone();
role1.setName("玩家1");
GameRole role2 = prototype.clone();
role2.setName("玩家2");
GameRole role3 = prototype.clone();
role3.setName("玩家3");
// 问题:修改原型的装备,会影响所有克隆对象(因为引用同一份)
prototype.getEquipment().setWeapon("铁剑");
System.out.println(role1.getEquipment().getWeapon()); // 输出"铁剑"(被影响)
}
}
2. 深克隆:复制 "全部细节"
深克隆会递归复制所有属性,包括引用类型 ------ 新对象和原对象的引用属性完全独立,修改互不影响。常见实现方式有两种:
- 手动对引用类型属性递归克隆;
- 通过序列化(将对象转成字节流再恢复)实现(更通用)。
序列化实现深克隆示例:
java
import java.io.*;
// 注意:需要实现Serializable接口
class GameRole implements Cloneable, Serializable {
private String name;
private int level;
private Equipment equipment; // 引用类型也需实现Serializable
// 深克隆:通过序列化
public GameRole deepClone() throws IOException, ClassNotFoundException {
// 1. 序列化:将对象写入字节流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 2. 反序列化:从字节流恢复对象(新对象)
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (GameRole) ois.readObject();
}
// 其他代码省略
}
// 装备类也需实现Serializable
class Equipment implements Serializable {
private String weapon;
// 代码省略
}
// 测试深克隆
public class DeepCloneDemo {
public static void main(String[] args) throws Exception {
Equipment equip = new Equipment("木剑");
GameRole prototype = new GameRole("原型角色", 1, equip);
GameRole role1 = prototype.deepClone();
role1.setName("玩家1");
// 修改原型的装备,克隆对象不受影响
prototype.getEquipment().setWeapon("铁剑");
System.out.println(role1.getEquipment().getWeapon()); // 输出"木剑"(独立)
}
}
三、适合用原型模式的 3 类场景
- 对象创建成本高 :如果对象构造需要大量资源(比如数据库查询、文件 IO),克隆比
new更高效(比如报表系统中,批量生成结构相同、数据略有差异的报表); - 需要动态生成对象:比如插件系统,无法提前知道要创建的对象类型,可通过克隆已加载的插件实例扩展功能;
- 避免构造函数副作用:如果构造函数中有非初始化逻辑(比如注册监听器),重复调用可能导致意外(克隆可跳过构造函数)。
四、避坑指南
- 浅克隆可能导致 "意外共享":如果对象包含引用类型,优先用深克隆(除非明确需要共享引用);
- 序列化深克隆的限制:被克隆的类及所有引用类型必须实现
Serializable, transient 修饰的属性不会被克隆; - 克隆与构造函数:
Object.clone()不会调用构造函数,若初始化逻辑依赖构造函数,需在克隆后手动补充。
总结
原型模式是 "高效复制" 的代名词 ------ 当你需要批量创建相似对象时,与其重复new和初始化,不如 "克隆原型 + 修改细节"。记住:浅克隆适合简单对象,深克隆适合含引用类型的复杂对象,根据场景选择即可。