周四,北京像是突然把温度拧低了一档。
说是要下雪,暖气却还没来,屋里最低到了 15℃。早上出门,小王把外套裹紧了些。想到再上两天就能休息,心里多少有了点盼头。
到了公司,还是日常开发,没什么新鲜的大事。只是今天还有一件躲不开的事:提周报。
他打开飞书里上周那份。熟悉的栏目,熟悉的几行字,连"下周计划"都还在。周报这东西,有时候真像鸡肋:写着花工夫,写完又不知道有多少人看。
吐槽归吐槽,周报还是要提。小王把上周那份复制了一份,准备删掉过时的内容,再补上这一周真正做过的事。

暖气还没来,周报倒先到了
"复制一份再改",大概是对付重复文档最省力的办法。今天聊的原型模式(Prototype),干的也是这件事:不从零组装一个新对象,而是拿一个现成对象当样板,复制出新的。系列第十二篇。
01 从空白开始,重复的不只是打字
先把这个场景换成对象。一份周报有标题,也有一组条目。最简单的类可以这样写:
java
class WeeklyReport {
private String title;
private final List<String> items;
WeeklyReport(String title, List<String> items) {
this.title = title;
this.items = items;
}
void rename(String title) { this.title = title; }
void addItem(String item) { items.add(item); }
int itemCount() { return items.size(); }
}
标题可以换,条目可以加,这就是后面要复制的对象
上周那份,直接构造出来:
java
WeeklyReport lastWeek = new WeeklyReport(
"上周周报",
new ArrayList<>(List.of("修复登录问题", "整理联调记录"))
);
先把一份状态完整的上周周报准备好
只有两个字段时,这样写最省事,没必要为了套模式再加一层。
可真实对象未必这么轻。它可能要先读取配置、准备内部集合,再完成一串初始化。如果手上已经有一个状态合适的对象,而接下来还需要一个差不多的新对象,每次都从空白重装一遍,就未必划算了。
原型模式关心的正是这件事:以现有对象为起点,把它的状态带到新对象里。
02 照着上周那份,复制出一个新对象
给 WeeklyReport 加一个复制构造器,再留一个 copy() 入口:
java
WeeklyReport(WeeklyReport source) {
this(source.title, new ArrayList<>(source.items));
}
WeeklyReport copy() {
return new WeeklyReport(this);
}
复制规则收在类里,调用方只说"照着这份复制"
用起来,就是小王刚才那个动作:
java
WeeklyReport thisWeek = lastWeek.copy();
thisWeek.rename("本周周报");
thisWeek.addItem("补充本周联调结果");
System.out.println(lastWeek.itemCount()); // 2
System.out.println(thisWeek.itemCount()); // 3
本周加了一条,上周仍然保留原来的两条
lastWeek 是原型,thisWeek 是由它复制出来的新对象。标题换了,条目加了,上周那份没跟着动。
这里并没有让 new 消失。构造过程只是被收进了复制逻辑里,调用方不必再逐项组装。copy() 也不是 Java 的关键字,是我们自己定义的方法名。
Java 确实提供了 Object.clone()。它默认逐个字段复制,引用字段仍然指向原来的对象,不会自动替你完成深拷贝。实际代码里也常用 copy() 或复制构造器,把复制规则明明白白写出来,不必依赖 Cloneable 那套约定。
03 两份周报,怎么会改到同一张清单
先看一种更容易写、也更容易出事的版本:
java
WeeklyReport shallowCopy() {
return new WeeklyReport(title, items);
}
WeeklyReport draft = lastWeek.shallowCopy();
draft.addItem("周三补充的进展");
System.out.println(lastWeek.itemCount()); // 3
draft 多了一条,lastWeek 也从两条变成了三条
明明有两个 WeeklyReport,为什么上周那份还是被改了?
因为 shallowCopy() 只复制了外壳。两个对象的 items 指向同一个 ArrayList,从哪一边往里加,另一边看到的都会变。这就是浅拷贝:对象本身换了,内部引用仍可能共用。
rename() 没惹这个祸。它只是把当前对象的 title 换成另一个字符串,而且 String 本身不可变。真正需要盯住的是 items 这种可变状态。
前面的复制构造器里,new ArrayList<>(source.items) 单独造了一张清单,所以原对象和复制结果互不影响。对当前这个只有字符串条目的例子来说,这已经够了。
但别急着把"外面套一个 new ArrayList"记成深拷贝的标准答案。如果清单里装的是可变的 Task 对象,新清单和旧清单仍会共用每一个 Task。需要独立修改时,还得继续复制每个 Task。深拷贝不是某个固定 API,而是把需要独立的状态一路复制到位。

是不是独立,不能只看外壳,还得看里面的引用
04 复制省掉的是装配,不是判断
这里有两处类比失真,得挑明。
第一,现实里复制文档,通常会得到两份互不影响的文件。浅拷贝的坑不是文档软件的"复制"按钮天然带来的,它对应的是代码里两个对象仍然引用同一份可变数据。周报类比只负责说明"以旧对象为起点",不能拿来推断所有软件里的复制行为。
第二,复制上周周报,也会把已经结束的条目、过时的风险、不再适用的计划一起带过来。原型只负责复制状态,不负责审查状态。哪些该留、哪些该删、哪些必须更新,仍然得由人判断。
这也回到了"周报很鸡肋"的感觉。**如果每周只是换个日期,复制得再快,也不会多出一点新信息。**如果周报要同步进展、暴露风险、对齐接下来的安排,从上周那份接着改,倒能省掉不少重复劳动。
AI 可以很快补出复制构造器,但哪些引用允许共享、哪些状态必须独立、哪些内容不该带进本周,它都不能只凭一段类定义替你拍板。
05 跟工厂、模板方法,分别差在哪儿
工厂模式(Day 2)管"怎么得到一个满足要求的新对象" :根据名称、配置或类型,把创建过程收起来。原型模式管"以哪个现成对象为状态样板":重点不是选哪个类,而是把已有对象的状态复制过去。
两者并不互斥。实际项目里可以预先注册一批原型,工厂根据名称取出其中一个,再调用 copy()。工厂负责找到样板,原型负责复制样板。
模板方法模式(Day 3)管"事情的执行骨架怎么固定":父类写死步骤顺序,把部分步骤交给子类实现。周报的固定栏目只是一个文档结构,不会因为长得像"模板",就自动变成模板方法模式。
一个复制对象状态,一个固定算法步骤,别被"模板"两个字带偏了。
06 什么时候,别急着 copy
构造本来就很简单时,直接 new。 几个字段一眼能填完,再引入一套原型管理,只是多绕一步。
共享关系没说清时,先别复制。 哪些字段可以共用,哪些必须独立,取决于对象的所有权和业务语义。无脑深拷贝整个对象图,可能又慢又复杂,还可能把不该复制的资源一起卷进来。
对象本身不可变时,未必需要复制。 不可变对象可以安全共享,为了"看起来像新对象"再复制一遍,通常没有收益。
数据库连接、线程、打开的文件句柄,也别指望靠 copy() 复制。 它们背后连接着外部资源,不是把字段逐个抄一遍,就能得到第二个可用资源。
判断标准可以落在一句话上:手上是否已有状态合适的对象,而现在需要一个能从它出发、并按预期独立变化的新对象? 是,原型才有用武之地。
07 别让周报只剩一个新日期
小王看着复制来的周报,把上周已经结束的条目删掉,补上这周真正做过的事。复制帮他省下了重新搭结构的时间,却没替他完成最要紧的那一步。
周报是不是鸡肋,也不是一个 copy() 能回答的。写完有没有人看,能不能让协作少一点信息差,得看团队怎么用它。
天还冷,暖气还没来。工作照样往前走。好在再上两天,就能缓缓了。
跟前面十一篇对一句:单例管造几个,工厂管怎么造,模板方法管事情怎么走,策略管这一步用什么做法,装饰器管外面加几层,命令管把要做的事做成对象,代理管谁能碰到真东西,观察者管变化后通知谁,外观管一堆零件只留一个入口,状态管当前状态下怎么做,责任链管请求由哪一环接住,原型管拿哪个现成对象复制。
带走一句话:原型 = 以现有对象为样板复制新对象;需要独立修改时,还要把内部共享的可变状态复制清楚。
复制能让你不必从空白开始,但本周要写什么,仍然得由你回答。
暖气早点来吧。周报嘛,至少别只剩下一个新日期。
参考资料
- 《设计模式:可复用面向对象软件的基础》(GoF 原著)------原型模式的原始定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象
- Refactoring Guru · Prototype------原型模式的结构、复制构造器、浅拷贝与深拷贝的区别
- Oracle Java API · Object.clone()(Java 21 文档)------
Cloneable标记约定、默认字段复制行为,以及浅拷贝与深拷贝的说明