系列定位软考软件设计师 / 系统架构设计师 — 创建型模式专题第 5 讲考察分值上午题 1-2 分下午题常作为代码填空或类图识别出现难度等级⭐⭐⭐☆☆概念简单但深拷贝与浅拷贝的区分是高频失分点一、考纲定位与模式定义1.1 考纲要求原型模式在软考中属于创建型模式的基础内容。考察形式包括上午选择题判断场景描述所属模式识别深拷贝与浅拷贝的区别判断clone()方法的使用规范区分原型模式与工厂模式的适用场景下午设计题补全原型类的clone()方法实现补全原型管理器PrototypeManager的注册与获取逻辑识别类图中原型角色的典型特征实现Cloneable接口 重写clone()1.2 模式定义原型模式用原型实例指定创建对象的种类并且通过复制这些原型创建新的对象。核心意图当直接创建对象的代价较大如需要复杂初始化、需要从数据库/网络加载数据或者需要保留现有对象的状态作为新对象的起点时通过克隆现有对象来创建新对象而不是从头new一个。二、UML 类图与角色划分┌─────────────────────────────────────────────────────────┐ │ Client │ │ main() │ │ ├── prototype.clone() // 复制原型 │ │ └── 修改副本属性 │ └───────────────────────┬─────────────────────────────────┘ │ clones ▼ ┌─────────────────────────────────────────────────────────┐ │ interface │ │ Prototype │ │ clone(): Prototype │ └───────────────────────┬─────────────────────────────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ ConcretePrototypeA │ │ ConcretePrototypeB │ │ clone() │ │ clone() │ │ return copy │ │ return copy │ └─────────────────────┘ └─────────────────────┘角色职责软考填空关键词Prototype抽象原型声明clone()接口定义复制自身的规范interface/abstract classclone()ConcretePrototype具体原型实现clone()方法完成自身的复制浅拷贝或深拷贝implements Cloneablepublic Object clone()Client客户端通过调用原型的clone()方法创建新对象再根据需求修改副本prototype.clone()类图识别要点软考类图中原型模式的核心特征是类实现Cloneable接口标记接口并重写clone()方法返回自身副本。如果类图里有clone()方法且返回自身类型大概率是原型模式。三、场景一浅拷贝原型基础版引用类型共享业务背景文档模板系统每个新文档都基于一个 标准模板 复制而来然后修改标题和作者。模板包含基本格式字体、页边距和附件列表引用类型。使用浅拷贝实现。说明浅拷贝是原型模式的最基础实现。它复制对象的基本类型字段但引用类型字段如 List、数组、对象引用只复制引用地址新旧对象共享同一个引用对象。这是软考最常考的陷阱点。3.1 代码实现import java.util.ArrayList; import java.util.List; public class DocumentTemplate implements Cloneable { private String title; private String font; private ListString attachments; public DocumentTemplate(String title, String font) { this.title title; this.font font; this.attachments new ArrayList(); } public void addAttachment(String file) { attachments.add(file); } Override public DocumentTemplate clone() { try { return (DocumentTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(克隆失败, e); } } public void setTitle(String title) { this.title title; } public void setFont(String font) { this.font font; } Override public String toString() { return DocumentTemplate{title title , font font , attachments attachments }; } } public class Client { public static void main(String[] args) { DocumentTemplate prototype new DocumentTemplate(标准模板, 宋体 12号); prototype.addAttachment(公司Logo.png); prototype.addAttachment(页眉模板.docx); System.out.println(原型: prototype); DocumentTemplate doc1 prototype.clone(); doc1.setTitle(2024年Q2销售报告); doc1.setFont(黑体 14号); DocumentTemplate doc2 prototype.clone(); doc2.setTitle(2024年Q3预算方案); doc1.addAttachment(销售数据.xlsx); System.out.println(doc1: doc1); System.out.println(doc2: doc2); System.out.println(原型: prototype); } }3.2 运行结果与问题分析原型: DocumentTemplate{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]} doc1: DocumentTemplate{title2024年Q2销售报告, font黑体 14号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} doc2: DocumentTemplate{title2024年Q3预算方案, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} 原型: DocumentTemplate{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]}问题暴露doc1添加了一个附件 销售数据.xlsx但doc2和prototype的附件列表里也莫名其妙地多出了这个文件原因浅拷贝只复制了attachments这个引用地址原型、doc1、doc2三个对象指向的是同一个 ArrayList 对象。任何一个对象修改这个列表其他对象都会 被传染。软考高频考点浅拷贝中引用类型字段共享同一个对象修改一个会影响所有副本。这是选择题和填空题的必考陷阱。四、场景二深拷贝原型解决引用类型共享问题业务背景同样是文档模板系统但要求每个克隆出来的文档都有独立的附件列表互不影响。使用深拷贝实现。说明深拷贝不仅复制对象本身还递归复制对象内部所有的引用类型字段确保新旧对象完全独立。实现方式有两种① 手动逐个复制引用对象② 利用序列化Serializable自动深拷贝。软考代码填空通常考手动逐个复制。4.1 代码实现手动逐个复制引用对象import java.util.ArrayList; import java.util.List; public class DocumentTemplateDeep implements Cloneable { private String title; private String font; private ListString attachments; public DocumentTemplateDeep(String title, String font) { this.title title; this.font font; this.attachments new ArrayList(); } public void addAttachment(String file) { attachments.add(file); } Override public DocumentTemplateDeep clone() { try { DocumentTemplateDeep copy (DocumentTemplateDeep) super.clone(); copy.attachments new ArrayList(this.attachments); return copy; } catch (CloneNotSupportedException e) { throw new RuntimeException(克隆失败, e); } } public void setTitle(String title) { this.title title; } public void setFont(String font) { this.font font; } Override public String toString() { return DocumentTemplateDeep{title title , font font , attachments attachments }; } } public class Client { public static void main(String[] args) { DocumentTemplateDeep prototype new DocumentTemplateDeep(标准模板, 宋体 12号); prototype.addAttachment(公司Logo.png); prototype.addAttachment(页眉模板.docx); System.out.println(原型: prototype); DocumentTemplateDeep doc1 prototype.clone(); doc1.setTitle(2024年Q2销售报告); doc1.addAttachment(销售数据.xlsx); DocumentTemplateDeep doc2 prototype.clone(); doc2.setTitle(2024年Q3预算方案); doc2.addAttachment(预算表.xlsx); System.out.println(doc1: doc1); System.out.println(doc2: doc2); System.out.println(原型: prototype); } }4.2 运行结果原型: DocumentTemplateDeep{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]} doc1: DocumentTemplateDeep{title2024年Q2销售报告, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} doc2: DocumentTemplateDeep{title2024年Q3预算方案, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 预算表.xlsx]} 原型: DocumentTemplateDeep{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]}结果分析每个对象的附件列表完全独立修改互不影响。深拷贝通过new ArrayList(this.attachments)创建了一个全新的 List 对象并复制了所有元素。软考要点如果原型类里有引用类型字段如List、Map、自定义对象clone()方法中必须手动创建新的引用对象并复制数据否则就是浅拷贝。这是下午代码填空的核心判分点。五、场景三原型管理器通过注册表管理多个原型业务背景系统中有多种文档模板合同模板、报告模板、通知模板客户端不需要知道具体模板类只需要通过名称从原型管理器中获取并克隆。这相当于给原型模式加了一层 工厂 的封装。说明原型管理器是原型模式的一种扩展用法软考下午题可能以 根据名称获取克隆对象 的形式考察。5.1 代码实现import java.util.HashMap; import java.util.Map; public interface DocumentPrototype extends Cloneable { DocumentPrototype clone(); void display(); } public class ContractTemplate implements DocumentPrototype { private String templateName 标准合同模板; private String content 合同正文占位; Override public ContractTemplate clone() { try { return (ContractTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } Override public void display() { System.out.println([合同模板] templateName : content); } public void setContent(String content) { this.content content; } } public class ReportTemplate implements DocumentPrototype { private String templateName 季度报告模板; private String header 报告页眉占位; Override public ReportTemplate clone() { try { return (ReportTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } Override public void display() { System.out.println([报告模板] templateName : header); } public void setHeader(String header) { this.header header; } } public class PrototypeManager { private static final MapString, DocumentPrototype registry new HashMap(); static { registry.put(contract, new ContractTemplate()); registry.put(report, new ReportTemplate()); } public static DocumentPrototype createDocument(String type) { DocumentPrototype prototype registry.get(type); if (prototype null) { throw new IllegalArgumentException(未知模板类型: type); } return prototype.clone(); } public static void register(String type, DocumentPrototype prototype) { registry.put(type, prototype); } } public class Client { public static void main(String[] args) { DocumentPrototype doc1 PrototypeManager.createDocument(contract); doc1.display(); DocumentPrototype doc2 PrototypeManager.createDocument(contract); doc2.display(); DocumentPrototype doc3 PrototypeManager.createDocument(report); doc3.display(); System.out.println(doc1 doc2 ? (doc1 doc2)); } }关键体会原型管理器将 原型注册 和 克隆获取 封装起来客户端完全不需要new任何具体类也不需要知道原型类的名字。这体现了依赖倒置原则—— 客户端依赖抽象原型接口而不是具体类。六、三种场景对比与演进思路维度场景一浅拷贝原型场景二深拷贝原型场景三原型管理器拷贝深度浅拷贝引用类型共享深拷贝引用类型独立取决于注册的原型是浅还是深引用类型处理共享原引用新建引用对象并复制数据由具体原型类决定是否有管理器❌ 无❌ 无✅ 有PrototypeManager客户端耦合依赖具体原型类依赖具体原型类依赖抽象原型接口适用场景对象无引用类型字段或允许共享对象有引用类型字段要求完全独立多原型统一管理客户端解耦软考考法选择题判断浅拷贝问题下午填空补全深拷贝逻辑下午填空补全注册表获取逻辑七、浅拷贝 vs 深拷贝核心对比对比项浅拷贝Shallow Copy深拷贝Deep Copy基本类型字段复制值复制值引用类型字段复制引用地址共享对象创建新对象并复制数据完全独立实现方式super.clone()即可super.clone() 手动复制引用对象性能快较慢需递归复制内存占用低共享引用对象高每个副本都有独立对象软考考点引用类型修改会相互影响引用类型修改互不影响典型问题修改一个副本的 List其他副本也变了无共享问题但实现复杂软考记忆口诀浅拷贝 复制壳子不复制里子深拷贝 壳子和里子全复制。八、软考高频考点与易混淆辨析8.1 高频考点考点内容模式分类创建型模式GoF 23 正式成员核心角色Prototype抽象原型、ConcretePrototype具体原型、Client可选 PrototypeManager实现前提具体原型类必须实现Cloneable接口标记接口否则调用clone()会抛CloneNotSupportedException浅拷贝问题引用类型字段共享修改一个会影响其他副本和原型深拷贝实现super.clone()后对引用类型字段手动new新对象并复制数据适用场景① 创建对象成本高需复杂初始化/IO② 需要保留对象当前状态作为新起点 ③ 系统需要独立于产品的创建、组合和表示与工厂模式区别工厂模式通过new创建全新对象原型模式通过clone()复制现有对象8.2 易混淆辨析原型模式 vs 工厂模式对比项原型模式工厂方法 / 抽象工厂创建方式复制现有对象clone()全新创建new依赖关系依赖现有对象的状态不依赖现有对象从零构建对象状态新对象初始状态与原型一致可在此基础上修改新对象按工厂逻辑初始化状态由工厂决定适用场景对象状态复杂、复制比重建更快对象创建逻辑需要抽象、多态类图特征有clone()方法有createXxx()/factoryMethod()方法关键区分如果类图里有clone()方法且类实现Cloneable→ 原型模式。如果类图里有createProduct()或factoryMethod()→ 工厂模式。九、真题风格模拟与代码填空模拟题 1上午选择题以下关于原型模式的叙述中正确的是。A. 原型模式属于结构型模式通过复制现有对象来创建新对象B. 在 Java 中实现原型模式只需重写clone()方法无需实现Cloneable接口C. 浅拷贝会复制对象的所有字段包括引用类型字段指向的对象因此副本与原型完全独立D. 原型模式适用于创建对象成本较高或需要基于现有对象状态创建新对象的场景答案D解析A 错误原型模式属于创建型模式不是结构型。B 错误Java 中调用Object.clone()的前提是实现Cloneable接口否则抛CloneNotSupportedException。C 错误浅拷贝只复制引用类型字段的地址不复制引用指向的对象因此副本与原型不独立共享引用对象。D 正确这是原型模式的核心适用场景。模拟题 2下午代码填空 — 浅拷贝与深拷贝判断某系统使用原型模式实现工作流节点WorkFlowNode的复制。节点包含名称nameString 类型和前置节点列表prevNodesList 类型。以下代码为浅拷贝实现请补全1~2使其变为深拷贝。javaimport java.util.ArrayList; import java.util.List; public class WorkFlowNode implements Cloneable { private String name; private ListWorkFlowNode prevNodes; public WorkFlowNode(String name) { this.name name; this.prevNodes new ArrayList(); } public void addPrevNode(WorkFlowNode node) { prevNodes.add(node); } Override public WorkFlowNode clone() { try { WorkFlowNode copy (WorkFlowNode) super.clone(); (1)______ (2)______; return copy; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }答案(1)copy.prevNodes(2)new ArrayList(this.prevNodes)阅卷要点如果填copy.prevNodes new ArrayList(this.prevNodes)→满分。这是深拷贝 List 的标准写法新建了 List 并复制了元素引用。如果填copy.prevNodes this.prevNodes→零分。这是浅拷贝没有创建新 List。如果填copy.prevNodes new ArrayList(); copy.prevNodes.addAll(this.prevNodes)→满分。等价于new ArrayList(this.prevNodes)。模拟题 3下午代码填空 — 原型管理器某系统使用原型管理器管理多种形状Shape原型。请补全1~4。interface Shape extends Cloneable { Shape clone(); void draw(); } class Circle implements Shape { Override public Circle clone() { try { return (Circle) super.clone(); } catch (Exception e) { throw new RuntimeException(e); } } Override public void draw() { System.out.println(绘制圆形); } } class Rectangle implements Shape { Override public Rectangle clone() { try { return (Rectangle) super.clone(); } catch (Exception e) { throw new RuntimeException(e); } } Override public void draw() { System.out.println(绘制矩形); } } class ShapeManager { private static final MapString, Shape registry new HashMap(); static { registry.put(circle, new Circle()); registry.put(rectangle, new Rectangle()); } public static Shape createShape(String type) { Shape prototype (1)______; if (prototype null) { throw new IllegalArgumentException(未知形状: type); } return (2)______; } public static void register(String type, (3)______ prototype) { registry.put(type, prototype); } } public class Client { public static void main(String[] args) { Shape s1 ShapeManager.createShape(circle); Shape s2 ShapeManager.createShape(circle); System.out.println(s1 s2); // 输出 (4)______ } }答案(1)registry.get(type)(2)prototype.clone()(3)Shape(4)false阅卷要点(2) 必须填prototype.clone()不能填prototype。如果返回原型本身所有客户端拿到的是同一个对象违背了原型模式的初衷。(3) 必须是Shape抽象类型体现面向接口编程。(4) 输出false因为clone()创建的是新对象不是原对象。十、常见陷阱与注意事项陷阱 1误认为原型是结构型模式原型模式属于创建型模式因为它解决的是如何创建对象的问题通过复制。软考上午题如果问以下属于结构型模式的是选项里出现原型不能选。陷阱 2忘记实现Cloneable接口Java 中Object.clone()是protected方法且会检查对象是否实现了Cloneable标记接口。如果没实现就调用clone()会抛CloneNotSupportedException。软考代码填空里如果类声明为implements Cloneable那就是送分提示。陷阱 3深拷贝与浅拷贝判断失误这是软考最高频的失分点。浅拷贝super.clone()后直接返回。引用类型字段共享。深拷贝super.clone()后对引用类型字段手动new新对象。快速判断技巧看clone()方法里有没有对List、Map、数组、自定义对象等引用类型字段做new Xxx()操作。如果没有 → 浅拷贝如果有 → 深拷贝。陷阱 4混淆clone()与newclone()复制的是对象当前的状态包括已经修改过的字段值而new创建的是对象的初始状态由构造器决定。如果题目描述是 基于现有对象创建新对象保留现有状态 → 选原型模式。陷阱 5与单例模式混淆单例模式保证只有一个实例原型模式通过clone()可以产生无数个实例。两者是完全相反的思路。如果代码里既有getInstance()又有clone()不要混淆 —— 前者是单例后者是原型。陷阱 6深拷贝的递归问题如果对象内部有多层引用嵌套如A引用BB引用C手动深拷贝需要层层clone()。软考通常只考一层引用如List不会考多层嵌套。但如果遇到思路是递归调用每个引用对象的clone()。十一、总结要点内容定义用原型实例指定创建对象的种类并通过复制这些原型创建新对象分类创建型模式GoF 23 正式成员核心角色Prototype抽象原型、ConcretePrototype具体原型、Client客户端实现前提实现Cloneable接口重写clone()方法浅拷贝super.clone()引用类型字段共享修改相互影响深拷贝super.clone() 手动new引用对象并复制数据完全独立原型管理器用Map注册多个原型客户端通过名称获取克隆实现解耦与工厂模式区别原型通过clone()复制现有对象工厂通过new全新创建适用场景创建成本高、需保留现有状态、需独立于产品创建过程软考重点深拷贝与浅拷贝的区分clone()方法补全Cloneable接口的作用原型管理器的注册与获取答题技巧看到clone()方法、Cloneable接口 → 原型模式看到引用类型修改相互影响 → 浅拷贝问题系列预告创建型模式五兄弟简单工厂、工厂方法、抽象工厂、单例、建造者、原型已全部讲完。下一篇将开启结构型模式篇章从适配器模式开始 —— 当新旧接口不兼容时如何在不修改原有代码的前提下让原本因接口不匹配而无法工作的类能够协同工作。咱们下回见。
【软考设计模式】原型模式:“克隆“对象与深拷贝、浅拷贝精讲
发布时间:2026/7/9 4:27:17
系列定位软考软件设计师 / 系统架构设计师 — 创建型模式专题第 5 讲考察分值上午题 1-2 分下午题常作为代码填空或类图识别出现难度等级⭐⭐⭐☆☆概念简单但深拷贝与浅拷贝的区分是高频失分点一、考纲定位与模式定义1.1 考纲要求原型模式在软考中属于创建型模式的基础内容。考察形式包括上午选择题判断场景描述所属模式识别深拷贝与浅拷贝的区别判断clone()方法的使用规范区分原型模式与工厂模式的适用场景下午设计题补全原型类的clone()方法实现补全原型管理器PrototypeManager的注册与获取逻辑识别类图中原型角色的典型特征实现Cloneable接口 重写clone()1.2 模式定义原型模式用原型实例指定创建对象的种类并且通过复制这些原型创建新的对象。核心意图当直接创建对象的代价较大如需要复杂初始化、需要从数据库/网络加载数据或者需要保留现有对象的状态作为新对象的起点时通过克隆现有对象来创建新对象而不是从头new一个。二、UML 类图与角色划分┌─────────────────────────────────────────────────────────┐ │ Client │ │ main() │ │ ├── prototype.clone() // 复制原型 │ │ └── 修改副本属性 │ └───────────────────────┬─────────────────────────────────┘ │ clones ▼ ┌─────────────────────────────────────────────────────────┐ │ interface │ │ Prototype │ │ clone(): Prototype │ └───────────────────────┬─────────────────────────────────┘ ┌────────────┴────────────┐ ▼ ▼ ┌─────────────────────┐ ┌─────────────────────┐ │ ConcretePrototypeA │ │ ConcretePrototypeB │ │ clone() │ │ clone() │ │ return copy │ │ return copy │ └─────────────────────┘ └─────────────────────┘角色职责软考填空关键词Prototype抽象原型声明clone()接口定义复制自身的规范interface/abstract classclone()ConcretePrototype具体原型实现clone()方法完成自身的复制浅拷贝或深拷贝implements Cloneablepublic Object clone()Client客户端通过调用原型的clone()方法创建新对象再根据需求修改副本prototype.clone()类图识别要点软考类图中原型模式的核心特征是类实现Cloneable接口标记接口并重写clone()方法返回自身副本。如果类图里有clone()方法且返回自身类型大概率是原型模式。三、场景一浅拷贝原型基础版引用类型共享业务背景文档模板系统每个新文档都基于一个 标准模板 复制而来然后修改标题和作者。模板包含基本格式字体、页边距和附件列表引用类型。使用浅拷贝实现。说明浅拷贝是原型模式的最基础实现。它复制对象的基本类型字段但引用类型字段如 List、数组、对象引用只复制引用地址新旧对象共享同一个引用对象。这是软考最常考的陷阱点。3.1 代码实现import java.util.ArrayList; import java.util.List; public class DocumentTemplate implements Cloneable { private String title; private String font; private ListString attachments; public DocumentTemplate(String title, String font) { this.title title; this.font font; this.attachments new ArrayList(); } public void addAttachment(String file) { attachments.add(file); } Override public DocumentTemplate clone() { try { return (DocumentTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(克隆失败, e); } } public void setTitle(String title) { this.title title; } public void setFont(String font) { this.font font; } Override public String toString() { return DocumentTemplate{title title , font font , attachments attachments }; } } public class Client { public static void main(String[] args) { DocumentTemplate prototype new DocumentTemplate(标准模板, 宋体 12号); prototype.addAttachment(公司Logo.png); prototype.addAttachment(页眉模板.docx); System.out.println(原型: prototype); DocumentTemplate doc1 prototype.clone(); doc1.setTitle(2024年Q2销售报告); doc1.setFont(黑体 14号); DocumentTemplate doc2 prototype.clone(); doc2.setTitle(2024年Q3预算方案); doc1.addAttachment(销售数据.xlsx); System.out.println(doc1: doc1); System.out.println(doc2: doc2); System.out.println(原型: prototype); } }3.2 运行结果与问题分析原型: DocumentTemplate{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]} doc1: DocumentTemplate{title2024年Q2销售报告, font黑体 14号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} doc2: DocumentTemplate{title2024年Q3预算方案, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} 原型: DocumentTemplate{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]}问题暴露doc1添加了一个附件 销售数据.xlsx但doc2和prototype的附件列表里也莫名其妙地多出了这个文件原因浅拷贝只复制了attachments这个引用地址原型、doc1、doc2三个对象指向的是同一个 ArrayList 对象。任何一个对象修改这个列表其他对象都会 被传染。软考高频考点浅拷贝中引用类型字段共享同一个对象修改一个会影响所有副本。这是选择题和填空题的必考陷阱。四、场景二深拷贝原型解决引用类型共享问题业务背景同样是文档模板系统但要求每个克隆出来的文档都有独立的附件列表互不影响。使用深拷贝实现。说明深拷贝不仅复制对象本身还递归复制对象内部所有的引用类型字段确保新旧对象完全独立。实现方式有两种① 手动逐个复制引用对象② 利用序列化Serializable自动深拷贝。软考代码填空通常考手动逐个复制。4.1 代码实现手动逐个复制引用对象import java.util.ArrayList; import java.util.List; public class DocumentTemplateDeep implements Cloneable { private String title; private String font; private ListString attachments; public DocumentTemplateDeep(String title, String font) { this.title title; this.font font; this.attachments new ArrayList(); } public void addAttachment(String file) { attachments.add(file); } Override public DocumentTemplateDeep clone() { try { DocumentTemplateDeep copy (DocumentTemplateDeep) super.clone(); copy.attachments new ArrayList(this.attachments); return copy; } catch (CloneNotSupportedException e) { throw new RuntimeException(克隆失败, e); } } public void setTitle(String title) { this.title title; } public void setFont(String font) { this.font font; } Override public String toString() { return DocumentTemplateDeep{title title , font font , attachments attachments }; } } public class Client { public static void main(String[] args) { DocumentTemplateDeep prototype new DocumentTemplateDeep(标准模板, 宋体 12号); prototype.addAttachment(公司Logo.png); prototype.addAttachment(页眉模板.docx); System.out.println(原型: prototype); DocumentTemplateDeep doc1 prototype.clone(); doc1.setTitle(2024年Q2销售报告); doc1.addAttachment(销售数据.xlsx); DocumentTemplateDeep doc2 prototype.clone(); doc2.setTitle(2024年Q3预算方案); doc2.addAttachment(预算表.xlsx); System.out.println(doc1: doc1); System.out.println(doc2: doc2); System.out.println(原型: prototype); } }4.2 运行结果原型: DocumentTemplateDeep{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]} doc1: DocumentTemplateDeep{title2024年Q2销售报告, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 销售数据.xlsx]} doc2: DocumentTemplateDeep{title2024年Q3预算方案, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx, 预算表.xlsx]} 原型: DocumentTemplateDeep{title标准模板, font宋体 12号, attachments[公司Logo.png, 页眉模板.docx]}结果分析每个对象的附件列表完全独立修改互不影响。深拷贝通过new ArrayList(this.attachments)创建了一个全新的 List 对象并复制了所有元素。软考要点如果原型类里有引用类型字段如List、Map、自定义对象clone()方法中必须手动创建新的引用对象并复制数据否则就是浅拷贝。这是下午代码填空的核心判分点。五、场景三原型管理器通过注册表管理多个原型业务背景系统中有多种文档模板合同模板、报告模板、通知模板客户端不需要知道具体模板类只需要通过名称从原型管理器中获取并克隆。这相当于给原型模式加了一层 工厂 的封装。说明原型管理器是原型模式的一种扩展用法软考下午题可能以 根据名称获取克隆对象 的形式考察。5.1 代码实现import java.util.HashMap; import java.util.Map; public interface DocumentPrototype extends Cloneable { DocumentPrototype clone(); void display(); } public class ContractTemplate implements DocumentPrototype { private String templateName 标准合同模板; private String content 合同正文占位; Override public ContractTemplate clone() { try { return (ContractTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } Override public void display() { System.out.println([合同模板] templateName : content); } public void setContent(String content) { this.content content; } } public class ReportTemplate implements DocumentPrototype { private String templateName 季度报告模板; private String header 报告页眉占位; Override public ReportTemplate clone() { try { return (ReportTemplate) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } Override public void display() { System.out.println([报告模板] templateName : header); } public void setHeader(String header) { this.header header; } } public class PrototypeManager { private static final MapString, DocumentPrototype registry new HashMap(); static { registry.put(contract, new ContractTemplate()); registry.put(report, new ReportTemplate()); } public static DocumentPrototype createDocument(String type) { DocumentPrototype prototype registry.get(type); if (prototype null) { throw new IllegalArgumentException(未知模板类型: type); } return prototype.clone(); } public static void register(String type, DocumentPrototype prototype) { registry.put(type, prototype); } } public class Client { public static void main(String[] args) { DocumentPrototype doc1 PrototypeManager.createDocument(contract); doc1.display(); DocumentPrototype doc2 PrototypeManager.createDocument(contract); doc2.display(); DocumentPrototype doc3 PrototypeManager.createDocument(report); doc3.display(); System.out.println(doc1 doc2 ? (doc1 doc2)); } }关键体会原型管理器将 原型注册 和 克隆获取 封装起来客户端完全不需要new任何具体类也不需要知道原型类的名字。这体现了依赖倒置原则—— 客户端依赖抽象原型接口而不是具体类。六、三种场景对比与演进思路维度场景一浅拷贝原型场景二深拷贝原型场景三原型管理器拷贝深度浅拷贝引用类型共享深拷贝引用类型独立取决于注册的原型是浅还是深引用类型处理共享原引用新建引用对象并复制数据由具体原型类决定是否有管理器❌ 无❌ 无✅ 有PrototypeManager客户端耦合依赖具体原型类依赖具体原型类依赖抽象原型接口适用场景对象无引用类型字段或允许共享对象有引用类型字段要求完全独立多原型统一管理客户端解耦软考考法选择题判断浅拷贝问题下午填空补全深拷贝逻辑下午填空补全注册表获取逻辑七、浅拷贝 vs 深拷贝核心对比对比项浅拷贝Shallow Copy深拷贝Deep Copy基本类型字段复制值复制值引用类型字段复制引用地址共享对象创建新对象并复制数据完全独立实现方式super.clone()即可super.clone() 手动复制引用对象性能快较慢需递归复制内存占用低共享引用对象高每个副本都有独立对象软考考点引用类型修改会相互影响引用类型修改互不影响典型问题修改一个副本的 List其他副本也变了无共享问题但实现复杂软考记忆口诀浅拷贝 复制壳子不复制里子深拷贝 壳子和里子全复制。八、软考高频考点与易混淆辨析8.1 高频考点考点内容模式分类创建型模式GoF 23 正式成员核心角色Prototype抽象原型、ConcretePrototype具体原型、Client可选 PrototypeManager实现前提具体原型类必须实现Cloneable接口标记接口否则调用clone()会抛CloneNotSupportedException浅拷贝问题引用类型字段共享修改一个会影响其他副本和原型深拷贝实现super.clone()后对引用类型字段手动new新对象并复制数据适用场景① 创建对象成本高需复杂初始化/IO② 需要保留对象当前状态作为新起点 ③ 系统需要独立于产品的创建、组合和表示与工厂模式区别工厂模式通过new创建全新对象原型模式通过clone()复制现有对象8.2 易混淆辨析原型模式 vs 工厂模式对比项原型模式工厂方法 / 抽象工厂创建方式复制现有对象clone()全新创建new依赖关系依赖现有对象的状态不依赖现有对象从零构建对象状态新对象初始状态与原型一致可在此基础上修改新对象按工厂逻辑初始化状态由工厂决定适用场景对象状态复杂、复制比重建更快对象创建逻辑需要抽象、多态类图特征有clone()方法有createXxx()/factoryMethod()方法关键区分如果类图里有clone()方法且类实现Cloneable→ 原型模式。如果类图里有createProduct()或factoryMethod()→ 工厂模式。九、真题风格模拟与代码填空模拟题 1上午选择题以下关于原型模式的叙述中正确的是。A. 原型模式属于结构型模式通过复制现有对象来创建新对象B. 在 Java 中实现原型模式只需重写clone()方法无需实现Cloneable接口C. 浅拷贝会复制对象的所有字段包括引用类型字段指向的对象因此副本与原型完全独立D. 原型模式适用于创建对象成本较高或需要基于现有对象状态创建新对象的场景答案D解析A 错误原型模式属于创建型模式不是结构型。B 错误Java 中调用Object.clone()的前提是实现Cloneable接口否则抛CloneNotSupportedException。C 错误浅拷贝只复制引用类型字段的地址不复制引用指向的对象因此副本与原型不独立共享引用对象。D 正确这是原型模式的核心适用场景。模拟题 2下午代码填空 — 浅拷贝与深拷贝判断某系统使用原型模式实现工作流节点WorkFlowNode的复制。节点包含名称nameString 类型和前置节点列表prevNodesList 类型。以下代码为浅拷贝实现请补全1~2使其变为深拷贝。javaimport java.util.ArrayList; import java.util.List; public class WorkFlowNode implements Cloneable { private String name; private ListWorkFlowNode prevNodes; public WorkFlowNode(String name) { this.name name; this.prevNodes new ArrayList(); } public void addPrevNode(WorkFlowNode node) { prevNodes.add(node); } Override public WorkFlowNode clone() { try { WorkFlowNode copy (WorkFlowNode) super.clone(); (1)______ (2)______; return copy; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } }答案(1)copy.prevNodes(2)new ArrayList(this.prevNodes)阅卷要点如果填copy.prevNodes new ArrayList(this.prevNodes)→满分。这是深拷贝 List 的标准写法新建了 List 并复制了元素引用。如果填copy.prevNodes this.prevNodes→零分。这是浅拷贝没有创建新 List。如果填copy.prevNodes new ArrayList(); copy.prevNodes.addAll(this.prevNodes)→满分。等价于new ArrayList(this.prevNodes)。模拟题 3下午代码填空 — 原型管理器某系统使用原型管理器管理多种形状Shape原型。请补全1~4。interface Shape extends Cloneable { Shape clone(); void draw(); } class Circle implements Shape { Override public Circle clone() { try { return (Circle) super.clone(); } catch (Exception e) { throw new RuntimeException(e); } } Override public void draw() { System.out.println(绘制圆形); } } class Rectangle implements Shape { Override public Rectangle clone() { try { return (Rectangle) super.clone(); } catch (Exception e) { throw new RuntimeException(e); } } Override public void draw() { System.out.println(绘制矩形); } } class ShapeManager { private static final MapString, Shape registry new HashMap(); static { registry.put(circle, new Circle()); registry.put(rectangle, new Rectangle()); } public static Shape createShape(String type) { Shape prototype (1)______; if (prototype null) { throw new IllegalArgumentException(未知形状: type); } return (2)______; } public static void register(String type, (3)______ prototype) { registry.put(type, prototype); } } public class Client { public static void main(String[] args) { Shape s1 ShapeManager.createShape(circle); Shape s2 ShapeManager.createShape(circle); System.out.println(s1 s2); // 输出 (4)______ } }答案(1)registry.get(type)(2)prototype.clone()(3)Shape(4)false阅卷要点(2) 必须填prototype.clone()不能填prototype。如果返回原型本身所有客户端拿到的是同一个对象违背了原型模式的初衷。(3) 必须是Shape抽象类型体现面向接口编程。(4) 输出false因为clone()创建的是新对象不是原对象。十、常见陷阱与注意事项陷阱 1误认为原型是结构型模式原型模式属于创建型模式因为它解决的是如何创建对象的问题通过复制。软考上午题如果问以下属于结构型模式的是选项里出现原型不能选。陷阱 2忘记实现Cloneable接口Java 中Object.clone()是protected方法且会检查对象是否实现了Cloneable标记接口。如果没实现就调用clone()会抛CloneNotSupportedException。软考代码填空里如果类声明为implements Cloneable那就是送分提示。陷阱 3深拷贝与浅拷贝判断失误这是软考最高频的失分点。浅拷贝super.clone()后直接返回。引用类型字段共享。深拷贝super.clone()后对引用类型字段手动new新对象。快速判断技巧看clone()方法里有没有对List、Map、数组、自定义对象等引用类型字段做new Xxx()操作。如果没有 → 浅拷贝如果有 → 深拷贝。陷阱 4混淆clone()与newclone()复制的是对象当前的状态包括已经修改过的字段值而new创建的是对象的初始状态由构造器决定。如果题目描述是 基于现有对象创建新对象保留现有状态 → 选原型模式。陷阱 5与单例模式混淆单例模式保证只有一个实例原型模式通过clone()可以产生无数个实例。两者是完全相反的思路。如果代码里既有getInstance()又有clone()不要混淆 —— 前者是单例后者是原型。陷阱 6深拷贝的递归问题如果对象内部有多层引用嵌套如A引用BB引用C手动深拷贝需要层层clone()。软考通常只考一层引用如List不会考多层嵌套。但如果遇到思路是递归调用每个引用对象的clone()。十一、总结要点内容定义用原型实例指定创建对象的种类并通过复制这些原型创建新对象分类创建型模式GoF 23 正式成员核心角色Prototype抽象原型、ConcretePrototype具体原型、Client客户端实现前提实现Cloneable接口重写clone()方法浅拷贝super.clone()引用类型字段共享修改相互影响深拷贝super.clone() 手动new引用对象并复制数据完全独立原型管理器用Map注册多个原型客户端通过名称获取克隆实现解耦与工厂模式区别原型通过clone()复制现有对象工厂通过new全新创建适用场景创建成本高、需保留现有状态、需独立于产品创建过程软考重点深拷贝与浅拷贝的区分clone()方法补全Cloneable接口的作用原型管理器的注册与获取答题技巧看到clone()方法、Cloneable接口 → 原型模式看到引用类型修改相互影响 → 浅拷贝问题系列预告创建型模式五兄弟简单工厂、工厂方法、抽象工厂、单例、建造者、原型已全部讲完。下一篇将开启结构型模式篇章从适配器模式开始 —— 当新旧接口不兼容时如何在不修改原有代码的前提下让原本因接口不匹配而无法工作的类能够协同工作。咱们下回见。