设计模式 19:复制订单时共享了什么
复制一个报价草稿后,业务员把副本中第一项数量从 2 改成 9;原草稿是否也变成 9?如果只是调用 clone(),表层对象虽不同,里面的清单和可写商品项仍可能共享。复制的合同必须回答“哪个状态可共享”,而不是只检查 original != copied。
表层复制不等于隔离
Before.copy() 使用 Java Object.clone(),测试断言克隆后顶层身份不同、首个 Item 却是同一个引用;修改副本里的 quantity,原本的数量也变成 9。这是本例的反例,不是对所有 clone 实现的断言。After.copy() 用显式拷贝构造逐个新建 Item,还对构造时调用方传入的列表做同样的复制:既隔离副本,也隔离原始输入。负数量抛异常,失败前不改已有副本状态。
1 | |
Alternative.fromQuantities(2) 从明确的值重新构造对象,调用方已经有原始数据时可能比先找一个原型更直接。Prototype 的理由是新对象可以从现存配置或复杂模板衍生,并在预定的共享边界内复制;不应为了演示 clone 强迫所有类实现 Cloneable。只读不可变值可以安全共享,连接、锁、文件句柄、监听器等资源却不能盲目深拷贝。示例只含单线程内存 Item,不涉及循环对象图或资源生命周期。
| 设计 | 顶层身份 | 内层可写数量 |
|---|---|---|
Before |
新对象 | 仍共享,同改动 |
After |
新对象 | 新建每个 Item,修改互不影响 |
Alternative |
直接根据数值重建 | 不借用待复制对象 |
flowchart LR
Original[Before 原草稿] --> Shared[共享 Item]
Clone[Before.clone 副本] --> Shared
AfterOriginal[After 原草稿] --> Own[原 Item]
AfterCopy[After.copy 副本] --> New[新 Item]
验证与练习
在 examples/design-patterns/ 运行 ./mvnw -B -ntp -pl labs/19 -am test 和累计 ./mvnw -B -ntp verify,实际结果和原始输出见 examples/design-patterns/evidence/19/RUN.md。旧数量、浅拷贝共享、显式复制隔离与负数拒绝均由断言核对;性能和内存占用 NOT_RUN。
- 新增“每个商品项有可修改的标签列表”,先为新旧副本的标签隔离写失败断言,再修改拷贝边界并复跑旧数量合同。
- 商品元数据改为只读
record时,写断言证明安全共享同一个元数据引用;解释为何不能据此共享可写商品项。
参考资料
- GoF 原书公开图书馆 PDF,3.4 Prototype,目录页标注起始页 133:https://cpcc.chd.gov.in/Content/PDFs/w8UkV3tWNyEtsbUZSWJ7fVhuB9A3tsGYdm4w6VGzg2wUTNFYikqnvvFbbkiW2zmfspPEghd7QTamiMby3lVIBemrhdVWwt6rOQnm.pdf 。
- Java SE 21
Object.cloneJavadoc:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html#clone() 。 - Prototype 模式简定义,InfoWorld/JavaWorld:https://www.infoworld.com/article/2170730/design-patterns-the-big-picture-part-1-design-pattern-history-and-classification.html 。
- 本篇来源边界:
writing-plans/design-patterns/SOURCES.md。 - 实验代码:
examples/design-patterns/labs/19/。
上一节:18 构造请求何时检查不变量;下一节:20 唯一实例属于哪个范围。





