原型模式 (Prototype)
📖 通俗理解
想象你需要填写10份一模一样的申请表:
- 笨方法:每张表都从头开始填,写10遍姓名、地址、电话...
- 聪明方法:填好一份,然后复印9份,只改不同的地方
原型模式就是:通过复制已有对象来创建新对象,而不是重新创建。
🎯 解决什么问题?
问题场景:
- 创建对象成本很高(如从数据库加载、复杂计算)
- 需要创建很多相似的对象
- 对象的创建过程很复杂
解决方案:先创建一个"原型"对象,需要新对象时直接复制它。
🌰 生活中的例子
- 复印文件:不用重新打印,复印就行
- 细胞分裂:通过复制自己产生新细胞
- 游戏存档:复制一个存档点
- 邮件模板:复制模板,修改收件人
💻 Java 代码实现
Java 中实现原型模式很简单,实现 Cloneable 接口,重写 clone() 方法即可。
方式一:浅拷贝
/**
* 简历类
*/
public class Resume implements Cloneable {
private String name;
private int age;
private String education;
private String workExperience;
public Resume(String name) {
this.name = name;
}
public void setPersonalInfo(int age, String education) {
this.age = age;
this.education = education;
}
public void setWorkExperience(String workExperience) {
this.workExperience = workExperience;
}
public void display() {
System.out.println("=== 简历 ===");
System.out.println("姓名: " + name);
System.out.println("年龄: " + age);
System.out.println("学历: " + education);
System.out.println("工作经历: " + workExperience);
}
// 克隆方法
@Override
public Resume clone() {
try {
return (Resume) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
使用方式:
public class Client {
public static void main(String[] args) {
// 创建原型简历
Resume prototype = new Resume("张三");
prototype.setPersonalInfo(25, "本科");
prototype.setWorkExperience("2020-2023: ABC 公司 Java 开发");
// 复制简历,投递给不同公司
Resume resume1 = prototype.clone();
resume1.setWorkExperience("2020-2023: ABC 公司 Java 开发\n求职意向: 阿里巴巴");
Resume resume2 = prototype.clone();
resume2.setWorkExperience("2020-2023: ABC 公司 Java 开发\n求职意向: 腾讯");
Resume resume3 = prototype.clone();
resume3.setWorkExperience("2020-2023: ABC 公司 Java 开发\n求职意向: 字节跳动");
// 显示所有简历
resume1.display();
System.out.println();
resume2.display();
System.out.println();
resume3.display();
}
}
🚨 浅拷贝的问题
浅拷贝只复制对象本身,不复制对象内部的引用对象!
public class ShallowCopyProblem {
public static void main(String[] args) {
// 原型对象
Person prototype = new Person("张三");
prototype.setAddress(new Address("北京", "朝阳区"));
// 浅拷贝
Person copy = prototype.clone();
// 修改拷贝对象的地址
copy.getAddress().setCity("上海");
// 问题:原型对象的地址也被改了!
System.out.println("原型地址: " + prototype.getAddress().getCity()); // 上海
System.out.println("拷贝地址: " + copy.getAddress().getCity()); // 上海
}
}
方式二:深拷贝(推荐)
深拷贝会递归复制所有引用对象。
方法1:手动深拷贝
/**
* 地址类
*/
public class Address implements Cloneable {
private String city;
private String district;
public Address(String city, String district) {
this.city = city;
this.district = district;
}
// getter 和 setter...
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
/**
* 员工类 - 深拷贝实现
*/
public class Employee implements Cloneable {
private String name;
private int age;
private Address address; // 引用类型
public Employee(String name, int age) {
this.name = name;
this.age = age;
}
// getter 和 setter...
// 深拷贝:手动克隆引用对象
@Override
public Employee clone() {
try {
Employee cloned = (Employee) super.clone();
// 关键:对引用类型也进行克隆
if (this.address != null) {
cloned.address = this.address.clone();
}
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
}
测试深拷贝:
public class DeepCopyTest {
public static void main(String[] args) {
// 创建原型
Employee prototype = new Employee("张三", 25);
prototype.setAddress(new Address("北京", "朝阳区"));
// 深拷贝
Employee copy = prototype.clone();
// 修改拷贝对象的地址
copy.getAddress().setCity("上海");
// 原型对象不受影响
System.out.println("原型地址: " + prototype.getAddress().getCity()); // 北京
System.out.println("拷贝地址: " + copy.getAddress().getCity()); // 上海
}
}
方法2:序列化深拷贝(推荐)
import java.io.*;
/**
* 使用序列化实现深拷贝
* 所有相关类都需要实现 Serializable 接口
*/
public class DeepCopyUtil {
@SuppressWarnings("unchecked")
public static <T extends Serializable> T deepCopy(T object) {
try {
// 序列化到字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(object);
oos.close();
// 从字节数组反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
T copy = (T) ois.readObject();
ois.close();
return copy;
} catch (Exception e) {
throw new RuntimeException("深拷贝失败", e);
}
}
}
// 使用
public class Client {
public static void main(String[] args) {
Employee prototype = new Employee("张三", 25);
prototype.setAddress(new Address("北京", "朝阳区"));
// 使用工具类深拷贝
Employee copy = DeepCopyUtil.deepCopy(prototype);
copy.getAddress().setCity("上海");
System.out.println("原型: " + prototype.getAddress().getCity()); // 北京
System.out.println("拷贝: " + copy.getAddress().getCity()); // 上海
}
}
🔥 实战案例:游戏角色克隆
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
/**
* 游戏角色
*/
public class GameCharacter implements Serializable, Cloneable {
private String name;
private int level;
private int health;
private int attack;
private List<String> skills;
private Equipment equipment;
public GameCharacter(String name) {
this.name = name;
this.level = 1;
this.health = 100;
this.attack = 10;
this.skills = new ArrayList<>();
this.equipment = new Equipment();
}
public void addSkill(String skill) {
skills.add(skill);
}
public void levelUp() {
level++;
health += 20;
attack += 5;
}
// 深拷贝
@Override
public GameCharacter clone() {
try {
GameCharacter cloned = (GameCharacter) super.clone();
// 克隆列表
cloned.skills = new ArrayList<>(this.skills);
// 克隆装备
cloned.equipment = this.equipment.clone();
return cloned;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return String.format("角色[%s] Lv.%d HP:%d ATK:%d 技能:%s 装备:%s",
name, level, health, attack, skills, equipment);
}
// getter 和 setter...
}
/**
* 装备
*/
public class Equipment implements Serializable, Cloneable {
private String weapon = "木剑";
private String armor = "布衣";
// getter 和 setter...
@Override
public Equipment clone() {
try {
return (Equipment) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
}
@Override
public String toString() {
return String.format("[武器:%s, 防具:%s]", weapon, armor);
}
}
使用方式:
public class GameDemo {
public static void main(String[] args) {
// 创建一个满级角色作为模板
GameCharacter template = new GameCharacter("战士模板");
for (int i = 0; i < 99; i++) {
template.levelUp();
}
template.addSkill("斩击");
template.addSkill("旋风斩");
template.addSkill("狂暴");
template.getEquipment().setWeapon("屠龙刀");
template.getEquipment().setArmor("黄金甲");
System.out.println("模板角色: " + template);
// 快速创建多个相同配置的角色
GameCharacter player1 = template.clone();
player1.setName("玩家1");
GameCharacter player2 = template.clone();
player2.setName("玩家2");
player2.addSkill("嘲讽"); // 额外技能
GameCharacter player3 = template.clone();
player3.setName("玩家3");
player3.getEquipment().setWeapon("倚天剑"); // 不同装备
System.out.println("\n克隆的角色:");
System.out.println(player1);
System.out.println(player2);
System.out.println(player3);
// 验证深拷贝:修改 player3 不影响模板
System.out.println("\n模板武器: " + template.getEquipment().getWeapon()); // 屠龙刀
}
}
📊 浅拷贝 vs 深拷贝
| 对比项 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 基本类型 | 值复制 ✅ | 值复制 ✅ |
| 引用类型 | 地址复制 ❌ | 对象复制 ✅ |
| 性能 | 快 | 慢 |
| 实现复杂度 | 简单 | 复杂 |
✅ 适用场景
- 创建对象成本高:如需要复杂计算或数据库查询
- 需要大量相似对象:如游戏中的怪物、文档模板
- 保护原对象:创建副本进行操作
⚠️ 注意事项
- 必须实现 Cloneable 接口
- 注意深拷贝和浅拷贝的区别
- final 字段无法在 clone() 中修改
小结
原型模式的核心:通过复制已有对象来创建新对象。
关键点:
- 浅拷贝:只复制对象本身
- 深拷贝:递归复制所有引用对象
- 推荐使用序列化方式实现深拷贝
👉 下一篇:适配器模式
