狗哥复印店里有一份简历模板,姓名、岗位和工作经历都填好了。
小明来做简历,内容跟模板差不多,只需要换个姓名。要是再拿一张空白纸重填,多少有点费劲。
Java 里也会遇到这种情况。已经有一个合适的对象,复制一份再改,通常更省事。

每次重新 new,有点重复
先不用设计模式,直接创建两份简历:
Resume dogResume = new Resume(
"狗哥", "Java 开发", "3 年项目经验"
);
Resume xiaomingResume = new Resume(
"小明", "Java 开发", "3 年项目经验"
);
代码没错。不过后两项明明一样,又写了一遍。
字段再多些,重复就更明显。改模板时,另一份还可能漏改,挺麻烦。
原型模式的思路很朴素:拿现成对象当模板,复制出一个新对象。
先让 Resume 可以复制
Java 里常见的标准写法,是让类实现 Cloneable。
public class Resume implements Cloneable {
private String name;
private final String job;
private final String experience;
public Resume(String name, String job, String experience) {
this.name = name;
this.job = job;
this.experience = experience;
}
}
Cloneable 像个标记,表示这个类允许调用 Object 提供的复制能力。
这里的原型,就是已经填好的 Resume。副本是从它复制出来的新简历。
把 clone() 公开出来
Object 里的 clone() 是 protected,外部不能直接调用。
所以在 Resume 里重写一次,把它改成 public:
@Override
public Resume clone() {
try {
return (Resume) super.clone();
} catch (CloneNotSupportedException exception) {
throw new AssertionError(exception);
}
}
super.clone() 负责复制当前对象。
返回值转成 Resume,调用的人就不用自己做强制类型转换。
这里捕获了 CloneNotSupportedException。咱们的类已经实现 Cloneable,正常不会走到异常分支,所以转成 AssertionError。
别被这几行吓着,真正干复制工作的就一句:
return (Resume) super.clone();
复制一份,再改姓名
现在可以直接拿狗哥的简历做模板:
Resume original = new Resume(
"狗哥", "Java 开发", "3 年项目经验"
);
Resume copy = original.clone();
copy.setName("小明");
输出结果:
原简历:姓名=狗哥, 岗位=Java 开发, 经历=3 年项目经验
新简历:姓名=小明, 岗位=Java 开发, 经历=3 年项目经验
岗位和经历直接沿用模板,只改姓名就行。
还有个重点:original 和 copy 不是同一个对象。
修改副本的姓名,原简历还是狗哥。各是各的,清清楚楚。
浅拷贝,先记住一个小坑
上面的三个字段都是字符串,复制起来看着很省心。
但 super.clone() 默认做的是 浅拷贝。对象里如果放了可修改的 List,原对象和副本可能还指向同一个列表。
比如简历里再加一个技能列表:
private List<String> skills;
只用 super.clone(),副本往 skills 里添加内容时,原简历也可能跟着变化。
解决办法是复制对象后,再给副本创建一份新列表:
Resume copy = (Resume) super.clone();
copy.skills = new ArrayList<>(this.skills);
return copy;
这篇先知道边界就够了。主例子里没有 List,不用一下子绕进去。
原型模式适合啥时候
没必要让所有对象都实现 Cloneable。
只有两三个字段,直接 new 已经很明白,再加 clone() 反而显得绕。
我一般会在这些情况下考虑:
- 已经有一个比较完整的模板对象;
- 新对象和模板大部分字段相同;
- 从头创建步骤多,容易漏掉配置;
- 业务里经常需要“复制一份再改”。
像简历模板、表单模板、游戏角色初始配置,都比较好理解。
狗哥收个尾
原型模式做的事不复杂:保留一个现成对象,需要时复制一份。
Java 的常见写法是实现 Cloneable,再公开重写 clone()。
对象里只有字符串这类字段,例子很好懂。碰到 List 之类的可变对象,多看一眼浅拷贝,别让两份数据串到一起。
下一篇咱聊 适配器模式。有些接口对不上,怎么加个小转换层,让旧代码接着用。