当前位置: 首页 > news >正文

【西瓜带你学设计模式 | 第六期 - 原型模式】原型模式 —— 浅拷贝与深拷贝实现、优缺点与适用场景

文章目录

    • 前言
    • 1. 原型模式是什么?
    • 2. 为什么要用原型?能解决什么问题?
    • 3. 原型模式的常见实现方式
    • 4. 方式一:克隆接口 `clone()`(核心:浅拷贝 vs 深拷贝)
      • 4.1 浅拷贝(浅复制)
      • 4.2 深拷贝(深复制)
    • 5. 方式二:注册式原型工厂(Prototype Registry)
      • 5.1 代码示例:原型注册中心 + clone
      • 5.2 使用方式
    • 6. 原型模式的优缺点
      • 6.1 优点
      • 6.2 缺点
    • 7. 原型模式的适用场景与注意事项
      • 7.1 适用场景
      • 7.2 注意事项
    • 8. 选型总结:怎么判断用不用原型?
    • 9. 总结

前言

在软件开发中,我们经常会遇到这种需求:一个对象创建成本很高、或者对象创建后需要在“多个相似但略有不同”的场景中复用。比如:

  • 大量相似的配置对象
  • 相似的报表/文档模板
  • 需要频繁复制、微调属性的模型对象

原型模式(Prototype Pattern)就是为了解决“如何高效地产生多个相似对象”而出现的。它的核心思路是:把创建对象的过程交给克隆(copy)


1. 原型模式是什么?

原型模式:用一个已经存在的对象作为“原型”,通过复制(克隆)该原型来创建新的对象,而不是直接 new。

你可以理解为:

  • 原对象 = 模板(Prototype)
  • 克隆 = 复制出新对象
  • 新对象 = 原对象的“副本”,通常只需要改动少量差异

2. 为什么要用原型?能解决什么问题?

原型模式主要解决以下问题:

  1. 对象创建开销大

    • 如果对象构建很复杂(读取模板、初始化大量字段、构建复杂结构),直接 new 可能很慢。
    • 复制通常更快。
  2. 创建过程复杂且易出错

    • 复杂初始化代码容易重复、难维护。
    • 用 clone 复用已验证过的初始化状态。
  3. 需要快速产生“相似对象”

    • 例如:同一份文档模板生成多份不同内容的文档。

3. 原型模式的常见实现方式

常见实现方式大致分为两类:

  1. 通过克隆接口clone()

    • 原型类提供clone方法,返回新对象副本
  2. 注册式原型工厂(Prototype Registry / Prototype Factory)

    • 系统启动时把常见原型注册到容器中
    • 需要时从容器取原型并 clone

4. 方式一:克隆接口clone()(核心:浅拷贝 vs 深拷贝)

4.1 浅拷贝(浅复制)

浅拷贝:只复制对象本身的字段值;对于字段如果还是引用类型,则引用也会被复制(导致共享同一个内嵌对象)。

classResumeimplementsCloneable{privateStringname;privateExperienceexperience;// 引用类型publicResume(Stringname,Experienceexperience){this.name=name;this.experience=experience;}@OverridepublicResumeclone()throwsCloneNotSupportedException{// 默认浅拷贝:克隆对象本体,但experience引用仍指向同一个对象return(Resume)super.clone();}// getter/setterpublicExperiencegetExperience(){returnexperience;}publicvoidsetExperience(Experienceexperience){this.experience=experience;}}classExperience{privateStringcompany;publicExperience(Stringcompany){this.company=company;}publicvoidsetCompany(Stringcompany){this.company=company;}publicStringgetCompany(){returncompany;}}

使用:

publicclassClient{publicstaticvoidmain(String[]args)throwsException{Resumer1=newResume("张三",newExperience("A公司"));Resumer2=r1.clone();r2.getExperience().setCompany("B公司");System.out.println(r1.getExperience().getCompany());// 会输出 B公司(共享引用)System.out.println(r2.getExperience().getCompany());// B公司}}

浅拷贝快,但可能带来“新对象改了会影响老对象”的问题。


4.2 深拷贝(深复制)

深拷贝:不仅复制对象本身的字段值,还要对引用字段所指向的对象进行复制,从而彻底隔离。

classResumeDeepimplementsCloneable{privateStringname;privateExperienceDeepexperience;publicResumeDeep(Stringname,ExperienceDeepexperience){this.name=name;this.experience=experience;}@OverridepublicResumeDeepclone()throwsCloneNotSupportedException{ResumeDeepcopy=(ResumeDeep)super.clone();// 深拷贝引用对象copy.experience=this.experience.clone();returncopy;}publicExperienceDeepgetExperience(){returnexperience;}}classExperienceDeepimplementsCloneable{privateStringcompany;publicExperienceDeep(Stringcompany){this.company=company;}@OverridepublicExperienceDeepclone()throwsCloneNotSupportedException{return(ExperienceDeep)super.clone();}publicvoidsetCompany(Stringcompany){this.company=company;}publicStringgetCompany(){returncompany;}}

验证:

publicclassClientDeep{publicstaticvoidmain(String[]args)throwsException{ResumeDeepr1=newResumeDeep("张三",newExperienceDeep("A公司"));ResumeDeepr2=r1.clone();r2.getExperience().setCompany("B公司");System.out.println(r1.getExperience().getCompany());// A公司System.out.println(r2.getExperience().getCompany());// B公司}}

深拷贝更安全,但复制成本更高。


5. 方式二:注册式原型工厂(Prototype Registry)

当你有很多“可复制的类型”,可以把原型对象放到一个注册表里,按 key 获取原型并 clone。

5.1 代码示例:原型注册中心 + clone

importjava.util.HashMap;importjava.util.Map;interfacePrototype<T>{Tclone();}classMailPrototypeimplementsPrototype<MailPrototype>{privateStringto;privateStringtitle;publicMailPrototype(Stringto,Stringtitle){this.to=to;this.title=title;}@OverridepublicMailPrototypeclone(){// 简化:这里就是深拷贝语义(本例字段都是基本/不可变类型)returnnewMailPrototype(this.to,this.title);}publicvoidsetTo(Stringto){this.to=to;}publicvoidsetTitle(Stringtitle){this.title=title;}@OverridepublicStringtoString(){return"Mail{to="+to+", title="+title+"}";}}classPrototypeRegistry{privatefinalMap<String,Prototype<?>>registry=newHashMap<>();publicvoidregister(Stringkey,Prototype<?>prototype){registry.put(key,prototype);}@SuppressWarnings("unchecked")public<T>TgetClone(Stringkey){return(T)registry.get(key).clone();}}

5.2 使用方式

publicclassClientRegistry{publicstaticvoidmain(String[]args){PrototypeRegistryregistry=newPrototypeRegistry();registry.register("mail",newMailPrototype("default@xx.com","默认标题"));MailPrototypemail1=registry.getClone("mail");mail1.setTo("a@xx.com");mail1.setTitle("标题A");MailPrototypemail2=registry.getClone("mail");mail2.setTo("b@xx.com");mail2.setTitle("标题B");System.out.println(mail1);System.out.println(mail2);}}

6. 原型模式的优缺点

6.1 优点

  • 避免复杂的创建过程:对象创建逻辑复用“已存在对象的状态”
  • 提高性能(在复杂对象创建场景中尤其明显)
  • 扩展方便:新增原型类型时只需注册/实现 clone
  • 解耦创建与使用:调用方不关心具体 new 细节

6.2 缺点

  • clone 实现成本高:深拷贝通常需要你手动处理引用对象
  • 易错点多
    • 浅拷贝导致共享引用(产生隐蔽 bug)
    • 深拷贝遗漏某些引用字段也可能出问题
  • 对“可变对象”更敏感:对象越可变,越要保证拷贝正确性

7. 原型模式的适用场景与注意事项

7.1 适用场景

  • 对象创建成本高,且需要大量生成相似对象
  • 系统希望将“创建对象”与“对象使用”解耦
  • 对象结构复杂,初始化过程繁琐
  • 需要产生“变化不大,但又要隔离”的多个实例

7.2 注意事项

  1. 明确使用浅拷贝还是深拷贝
    • 如果对象内部有可变引用字段:优先考虑深拷贝
  2. 克隆契约要清晰
    • clone 后的新对象应该与原对象“独立”还是“共享”?必须一致
  3. 避免 clone 滥用
    • 如果对象很轻量,直接 new 往往更简单清晰
  4. 循环引用场景要谨慎
    • 如果对象之间互相引用,深拷贝需要更复杂的策略(如图结构复制思路)

8. 选型总结:怎么判断用不用原型?

  • 对象创建很复杂/很耗时,并且能基于“现有对象状态”快速复制:用原型模式
  • 你需要生成很多“相似但略不同”的对象:用原型模式
  • 对象非常简单,用new就行:不必引入原型
  • 对象包含大量可变引用字段:需要特别重视深拷贝实现正确性

9. 总结

原型模式 = 用 clone 复制对象来创建新对象,适合高成本对象的快速复用;关键在于正确处理浅拷贝/深拷贝。

http://www.jsqmd.com/news/570007/

相关文章:

  • 为什么Windows需要HEIC缩略图支持:技术鸿沟的终结者
  • YOLOv8镜像实战测评:无需ModelScope也能稳定运行
  • 解密R2为负:从sklearn.metrics.r2_score看模型评估的陷阱
  • 30+平台突破限制:文档下载工具引发效率革命的全方位解决方案
  • G-Helper:5个强效步骤解决华硕笔记本电池续航衰减问题
  • 2026年知名的化工液体提纯分离设备/陶瓷膜分离设备/液体提纯分离设备直销厂家选哪家 - 行业平台推荐
  • 新手避坑指南:ADS8688寄存器读写那些事儿(附SPI驱动代码详解)
  • Cuvil for Python AI推理:3步绕过TensorRT兼容黑洞,实测推理延迟降低41.6%(附可复现错误码清单)
  • 3分钟搞定京东茅台自动抢购:Python脚本让你的抢购成功率翻倍
  • 2026年知名的三型瓶四型瓶检测设备/丙烷三型瓶四型瓶检测设备/乙炔三型瓶四型瓶检测设备/长管三型瓶四型瓶检测设备厂家选择指南 - 行业平台推荐
  • RVC在自媒体中的应用:批量生成多风格口播音频工作流
  • 2026年知名的手板模型/软胶复模手板模型/手板模型打样品牌厂家哪家靠谱 - 行业平台推荐
  • [已解决]Understanding and Fixing Conda Dependency Conflicts: The ‘requests‘ Module Case
  • GraphSAGE实战:用PyTorch Geometric实现工业级节点分类(含邻居采样优化技巧)
  • 从入门到实战:在快马平台用python构建你的第一个任务管理器应用
  • 告别静态DID!手把手教你用UDS 0x2C服务动态组合数据(附ISO14229实战报文)
  • 旧Mac重获新生:OpenCore Legacy Patcher让老旧设备支持最新macOS系统完整指南
  • SingleFile深度解析:现代网页归档的技术架构与实践指南
  • 2026年口碑好的陶瓷加热器/加热器/铸铜加热器生产商哪家强 - 行业平台推荐
  • 2026年华为云OpenClaw如何安装?配置百炼API零门槛10分钟步骤
  • 别再手动联网了!Linux开机自动连WiFi/有线网络的保姆级配置指南(CentOS/Ubuntu通用)
  • 5步修复损坏视频:面向内容创作者的UNTRUNC工具实战指南
  • 知网+DeepSeek:从选题到成稿的AI文献综述实战指南
  • 从播放卡顿到流媒体优化:深入MP4的stbl盒子,理解视频流畅播放的关键
  • 本地部署openclaw(window环境下)不用花钱买token版
  • 2026年口碑好的攀登安全绳/安全绳销售厂家哪家好 - 行业平台推荐
  • AI辅助开发新体验:描述你的色彩灵感,快马一键生成配色方案与应用
  • lvgl_v8之文本输入框代码示例
  • 电商多账号管理神器:用Python自动化实现1688/拼多多订单搬运
  • 2026年比较好的通风设备/通风设备风机/通风设备消声器/通风设备静压箱实力厂家如何选 - 行业平台推荐