Java设计模式精讲---04原型模式
你是否遇到过这样的场景:需要创建大量结构相似、仅少数属性不同的对象(比如 100 个游戏角色,基础属性相同,仅昵称和装备有差异)?如果每次都通过new关键字从头初始化,不仅代码冗余,还会因为重复执行构造函数中的复杂逻辑(比如数据库查询、网络请求)浪费性能。
这时候,原型模式(Prototype Pattern) 就能派上用场 —— 它的核心思想是:通过复制现有对象(原型)来创建新对象,而非重新初始化,就像现实中 “复制粘贴” 文档再修改细节,既高效又省力。
一、核心概念:克隆,而非新建
原型模式的本质是 “对象克隆”,它通过让对象自身提供复制方法,将对象的创建过程从 “主动新建” 转为 “被动复制”。关键角色只有两个:
- 原型接口(Prototype):定义克隆方法(比如 Java 中的
Cloneable接口,虽然是标记接口,但约定了clone()方法); - 具体原型(Concrete Prototype):实现克隆方法,完成自身复制。
二、实现方式:浅克隆 vs 深克隆
克隆的核心是 “复制对象的属性”,但根据属性类型(基本类型 / 引用类型),克隆分为两种:
1. 浅克隆:只复制 “表面”
浅克隆会复制对象的基本类型属性(int、String 等),但对于引用类型属性(比如对象、集合),仅复制引用地址 —— 新对象和原对象的引用属性会指向同一个内存地址,修改其中一个会影响另一个。
Java 实现示例(基于Cloneable接口):
// 具体原型:游戏角色
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("执行复杂初始化(比如加载角色模型)...");}// 实现克隆方法(浅克隆)@Overrideprotected 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. 深克隆:复制 “全部细节”
深克隆会递归复制所有属性,包括引用类型 —— 新对象和原对象的引用属性完全独立,修改互不影响。常见实现方式有两种:
- 手动对引用类型属性递归克隆;
- 通过序列化(将对象转成字节流再恢复)实现(更通用)。
序列化实现深克隆示例:
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和初始化,不如 “克隆原型 + 修改细节”。记住:浅克隆适合简单对象,深克隆适合含引用类型的复杂对象,根据场景选择即可。
