news 2026/8/29 2:40:13

设计模式学习(6) 23-4 原型模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式学习(6) 23-4 原型模式

文章目录

  • 0. 个人感悟
  • 1. 概念
  • 2. 适配场景(什么场景下使用)
  • 3. 实现方法(实现的思路)
    • 4. 代码示例
    • 4.1 传统方式
    • 4.2 原型模式
  • 5. 浅拷贝和深拷贝
    • 5.1 概念
    • 5.2 浅拷贝示例
    • 5.3 深拷贝实现1-重新clone方法,自己控制属性深拷贝(不推荐)
    • 5.4 深拷贝实现2-序列化(推荐)
  • 6. 原型模式优缺点

0. 个人感悟

  • 原型模式主要针对对象的复制场景,能够屏蔽复制的细节,对外提供复制能力
  • 很多场景下创建对象需要查库、计算等操作,重新new非常耗时
  • 原型模式的实现很简单,对java而言,实现Cloneable接口,重写clone()方法
  • 注意业务场景是需要深拷贝还是浅拷贝
  • 原型模式单独使用场景少,通常与其它模式一起使用,用于动态配置对象

1. 概念

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

翻译:
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
理解:

  • 核心思想: 不是通过new关键字直接创建对象,而是通过“克隆”一个已存在的实例来创建新的对象

2. 适配场景(什么场景下使用)

  • 创建成本高的对象: 比如当一个对象的初始化需要消耗大量资源(数据库加载,复杂计算等),且需要创建多个相似对象
  • 避免构造函数的约束: 原型模式复制对象不会调用构造函数,因此可以跳过构造函数的约束。所以这一点可以跳过某些单例模式实现,需要注意。
  • 需要动态性的配置对象: 当系统需要根据运行时的状态创建对象,而这些对象知识某个原型的变体时。
  • 保护性拷贝: 需要创建对象的副本来避免原始对象被意外修改。当然这里注意是深拷贝,后面会讨论。

3. 实现方法(实现的思路)

  • 抽象原型类: 规定原型对象必须实现的接口。jdk中已经定义了Cloneable接口,用于标识这个类可以安全的进行复制,否则运行时会抛出CloneNotSupported异常
  • 具体抽象类: 实现原型类的clone()方法。对java而言是重写object类的clone方法,注意其作用域 protected,一般的类无法调用,重写时注意将 clone() 方法的作用域修改为 public。
    在JDK中可以看到,Cloneable接口仅是一个标识:
packagejava.lang;/** * A class implements the {@code Cloneable} interface to * indicate to the {@link java.lang.Object#clone()} method that it * is legal for that method to make a * field-for-field copy of instances of that class. * <p> * Invoking Object's clone method on an instance that does not implement the * {@code Cloneable} interface results in the exception * {@code CloneNotSupportedException} being thrown. * <p> * By convention, classes that implement this interface should override * {@code Object.clone} (which is protected) with a public method. * See {@link java.lang.Object#clone()} for details on overriding this * method. * <p> * Note that this interface does <i>not</i> contain the {@code clone} method. * Therefore, it is not possible to clone an object merely by virtue of the * fact that it implements this interface. Even if the clone method is invoked * reflectively, there is no guarantee that it will succeed. * * @see java.lang.CloneNotSupportedException * @see java.lang.Object#clone() * @since 1.0 */publicinterfaceCloneable{}

说明部分的翻译:

实现 Cloneable 接口的类会向 Object.clone() 方法表明,该方法可以对该类的实例进行字段级复制。

如果对未实现 Cloneable 接口的实例调用 Object 的 clone 方法,则会抛出 CloneNotSupportedException 异常。

按照惯例,实现此接口的类应该使用公共方法重写 Object.clone(该方法为受保护方法)。有关重写此方法的详细信息,请参阅 Object.clone() 的文档。

请注意,此接口本身并不包含 clone 方法。因此,仅仅因为对象实现了此接口,并不能克隆该对象。即使通过反射调用 clone 方法,也不能保证克隆成功。

类图:

  • Prototype: 原型类,声明克隆接口
  • Realizetype: 具体原型,实现原型接口,可复制。
  • Client: 访问类

4. 代码示例

送请柬业务:
当需要给大量客人发送请柬,每份请柬只有personName不同,而messageaddress基本相同时。
不需要为每位客人重复构建完整的请柬对象,只需克隆并修改姓名即可。

4.1 传统方式

po

publicclassInvitation{StringpersonName;Stringmessage;Addressaddress;publicInvitation(StringpersonName,Stringmessage){this.personName=personName;this.message=message;}publicInvitation(StringpersonName,Stringmessage,Addressaddress){this.personName=personName;this.message=message;this.address=address;}// 省略}publicclassAddress{Stringstreet;Stringcity;publicAddress(Stringstreet,Stringcity){this.street=street;this.city=city;}// 省略}

客户端

publicclassOrgDemo{staticvoidmain(){// 给张三 西安AddressxianInfo=newAddress("朱雀大街","西安");Invitationinvitation=newInvitation("张三","诚挚邀请你参加年度庆典",xianInfo);// 给李四Invitationinvitation2=newInvitation("李四",invitation.getMessage(),invitation.getAddress());// 给王五Invitationinvitation3=newInvitation("王四",invitation.getMessage(),invitation.getAddress());}}

可以看到,需要重新new对象,并且需要知道细节来get set属性

4.2 原型模式

publicclassInvitationimplementsCloneable{StringpersonName;Stringmessage;Addressaddress;publicInvitation(StringpersonName,Stringmessage){this.personName=personName;this.message=message;}publicInvitation(StringpersonName,Stringmessage,Addressaddress){this.personName=personName;this.message=message;this.address=address;}@OverridepublicInvitationclone(){Invitationinvitation=null;try{invitation=(Invitation)super.clone();returninvitation;}catch(CloneNotSupportedExceptione){thrownewAssertionError();}}}publicclassAddress{Stringstreet;Stringcity;publicAddress(Stringstreet,Stringcity){this.street=street;this.city=city;}// 省略}

客户端

publicclassClient{staticvoidmain(){// 给张三 西安AddressxianInfo=newAddress("朱雀大街","西安");Invitationinvitation=newInvitation("张三","诚挚邀请你参加年度庆典",xianInfo);Invitationinvitation1=invitation.clone();invitation1.setPersonName("李四");Invitationinvitation2=invitation.clone();invitation2.setPersonName("王五");System.out.println(STR."\{invitation.getPersonName()}:\{invitation.getMessage()} 地点: \{invitation.getAddress().getCity()}");System.out.println(STR."\{invitation1.getPersonName()}:\{invitation1.getMessage()} 地点: \{invitation1.getAddress().getCity()}");System.out.println(STR."\{invitation2.getPersonName()}:\{invitation2.getMessage()} 地点: \{invitation2.getAddress().getCity()}");}}

不需要知道细节

5. 浅拷贝和深拷贝

5.1 概念

拷贝过程中会涉及对象属性如何拷贝问题,即是拷贝一个新对象还是只是拷贝地址(共享引用对象)
首先是java数据类型: 基本类型(8种基本类型及其包装类)、引用类型(字符串、数组、集合、其它)
对于基本类型,都是拷贝一个新值,即不影响原数据,对于引用类型:

  • 浅拷贝: 只拷贝地址(共享引用对象)。默认的clone()方法就是这个逻辑
  • 深拷贝: 拷贝新对象。也就是说对象修改不会影响原型。

5.2 浅拷贝示例

拷贝出来的实例,我们修改应用类型的属性进行观察

publicclassInvitationimplementsCloneable{StringpersonName;Stringmessage;Addressaddress;publicInvitation(StringpersonName,Stringmessage){this.personName=personName;this.message=message;}publicInvitation(StringpersonName,Stringmessage,Addressaddress){this.personName=personName;this.message=message;this.address=address;}@OverridepublicInvitationclone(){Invitationinvitation=null;try{invitation=(Invitation)super.clone();returninvitation;}catch(CloneNotSupportedExceptione){thrownewAssertionError();}}}publicclassAddress{Stringstreet;Stringcity;publicAddress(Stringstreet,Stringcity){this.street=street;this.city=city;}// 省略}
publicclassClient2{staticvoidmain(){// 给张三 西安AddressxianInfo=newAddress("朱雀大街","西安");Invitationinvitation=newInvitation("张三","诚挚邀请你参加年度庆典",xianInfo);Invitationinvitation1=invitation.clone();invitation1.setPersonName("李四");Invitationinvitation2=invitation.clone();invitation2.setPersonName("王五");invitation2.getAddress().setCity("上海");System.out.println(STR."\{invitation.getPersonName()}:\{invitation.getMessage()} 地点: \{invitation.getAddress().getCity()}");System.out.println(STR."\{invitation1.getPersonName()}:\{invitation1.getMessage()} 地点: \{invitation1.getAddress().getCity()}");System.out.println(STR."\{invitation2.getPersonName()}:\{invitation2.getMessage()} 地点: \{invitation2.getAddress().getCity()}");}}

输出:

张三:诚挚邀请你参加年度庆典 地点:上海 李四:诚挚邀请你参加年度庆典 地点:上海 王五:诚挚邀请你参加年度庆典 地点:上海

开始的地址是西安,在第二个实例中修改的地址为上海,结果输出可以看出,将所有地址都成了上海,也就是说原型和实例应用类型属性共享地址

5.3 深拷贝实现1-重新clone方法,自己控制属性深拷贝(不推荐)

publicclassInvitationimplementsCloneable{StringpersonName;Stringmessage;Addressaddress;publicInvitation(StringpersonName,Stringmessage){this.personName=personName;this.message=message;}publicInvitation(StringpersonName,Stringmessage,Addressaddress){this.personName=personName;this.message=message;this.address=address;}@OverridepublicInvitationclone(){Invitationinvitation=null;try{invitation=(Invitation)super.clone();// 这里新建对象invitation.setAddress(newAddress(invitation.getAddress().getStreet(),invitation.getAddress().getCity()));returninvitation;}catch(CloneNotSupportedExceptione){thrownewAssertionError();}}}publicclassAddress{Stringstreet;Stringcity;publicAddress(Stringstreet,Stringcity){this.street=street;this.city=city;}// 省略}

客户端:

publicclassClient2{staticvoidmain(){// 给张三 西安AddressxianInfo=newAddress("朱雀大街","西安");Invitationinvitation=newInvitation("张三","诚挚邀请你参加年度庆典",xianInfo);Invitationinvitation1=invitation.clone();invitation1.setPersonName("李四");Invitationinvitation2=invitation.clone();invitation2.setPersonName("王五");invitation2.getAddress().setCity("上海");System.out.println(STR."\{invitation.getPersonName()}:\{invitation.getMessage()} 地点: \{invitation.getAddress().getCity()}");System.out.println(STR."\{invitation1.getPersonName()}:\{invitation1.getMessage()} 地点: \{invitation1.getAddress().getCity()}");System.out.println(STR."\{invitation2.getPersonName()}:\{invitation2.getMessage()} 地点: \{invitation2.getAddress().getCity()}");}}

输出:

张三:诚挚邀请你参加年度庆典 地点:西安 李四:诚挚邀请你参加年度庆典 地点:西安 王五:诚挚邀请你参加年度庆典 地点:上海

可以看到实现了深拷贝。但是这种方式需要你知道代码实现细节,并且对于每个属性都需要自己手动去实现。

5.4 深拷贝实现2-序列化(推荐)

思路是把对象写到流里(序列化),再把流中对象读出来(反序列化)
工具:

publicclassCloneUtils{@SuppressWarnings("unchecked")publicstatic<TextendsSerializable>TdeepCopy(Tobject){if(object==null)returnnull;try(ByteArrayOutputStreambaos=newByteArrayOutputStream();ObjectOutputStreamoos=newObjectOutputStream(baos)){// 1. 序列化对象到字节数组oos.writeObject(object);oos.flush();try(ByteArrayInputStreambais=newByteArrayInputStream(baos.toByteArray());ObjectInputStreamois=newObjectInputStream(bais)){// 2. 反序列化创建新对象return(T)ois.readObject();}}catch(IOException|ClassNotFoundExceptione){thrownewRuntimeException("深拷贝失败",e);}}}

po实现序列化接口

publicclassInvitationimplementsSerializable{StringpersonName;Stringmessage;Addressaddress;publicInvitation(StringpersonName,Stringmessage){this.personName=personName;this.message=message;}publicInvitation(StringpersonName,Stringmessage,Addressaddress){this.personName=personName;this.message=message;this.address=address;}// 省略}publicclassAddressimplementsSerializable{Stringstreet;Stringcity;publicAddress(Stringstreet,Stringcity){this.street=street;this.city=city;}// 省略}

客户端:

publicclassClient2{staticvoidmain(){// 给张三 西安AddressxianInfo=newAddress("朱雀大街","西安");Invitationinvitation=newInvitation("张三","诚挚邀请你参加年度庆典",xianInfo);Invitationinvitation1=CloneUtils.deepCopy(invitation);invitation1.setPersonName("李四");Invitationinvitation2=CloneUtils.deepCopy(invitation);invitation2.setPersonName("王五");invitation2.getAddress().setCity("上海");System.out.println(STR."\{invitation.getPersonName()}:\{invitation.getMessage()} 地点: \{invitation.getAddress().getCity()}");System.out.println(STR."\{invitation1.getPersonName()}:\{invitation1.getMessage()} 地点: \{invitation1.getAddress().getCity()}");System.out.println(STR."\{invitation2.getPersonName()}:\{invitation2.getMessage()} 地点: \{invitation2.getAddress().getCity()}");}}

输出;

张三:诚挚邀请你参加年度庆典 地点:西安 李四:诚挚邀请你参加年度庆典 地点:西安 王五:诚挚邀请你参加年度庆典 地点:上海

6. 原型模式优缺点

结核1核(高内聚低耦合)4性(复用性 可读性 维护性 稳定性)7大原则(设计原则)

  • 优点:
    1. 高内聚低耦合: 创建逻辑和使用分离,降低耦合度
    2. 复用性: 高效复用已对象,避免重复初始化
    3. 开闭原则: 可以动态添加具体原型,无序修改现有代码
    4. 接口隔离: Cloneable接口最小化
    5. 迪米特法则: 封装克隆的实现,只需要调用clone()方法
  • 缺点:
    1. 维护性: 深拷贝实现复杂,维护困难

参考:

  • 韩顺平 Java设计模式
  • 张维鹏 Java设计模式之创建型:原型模式)
  • kosamino 设计模式之原型模式(Prototype)详解及代码示例
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/27 6:21:16

基于PaddlePaddle镜像的OCR流水线设计:适用于票据识别场景

基于PaddlePaddle镜像的OCR流水线设计&#xff1a;适用于票据识别场景 在金融、税务和物流等行业加速数字化转型的今天&#xff0c;每天都有海量的发票、收据、订单等纸质或扫描票据需要录入系统。传统依赖人工录入的方式不仅效率低下——一张发票平均耗时3到5分钟&#xff0c;…

作者头像 李华
网站建设 2026/8/26 17:05:46

ESP32-CAM图像传输协议解析:MJPG与TCP的性能对比分析

ESP32-CAM图像传输实战&#xff1a;MJPG与原始帧TCP的性能实测与选型指南你有没有遇到过这样的情况&#xff1f;调试ESP32-CAM时&#xff0c;画面卡顿、延迟高得离谱&#xff0c;甚至几秒才刷新一帧。换了个客户端还是老样子&#xff0c;Wi-Fi信号也不差——问题到底出在哪&…

作者头像 李华
网站建设 2026/8/29 2:32:27

es教程新手友好:配置本地开发环境步骤详解

从零开始搭建 Elasticsearch 本地开发环境&#xff1a;新手也能轻松上手 你是不是也曾在项目中听到“我们用的是 ELK 做日志分析”&#xff1f;或者面试时被问到&#xff1a;“你会用 Elasticsearch 写查询吗&#xff1f;”——如果你点头说会&#xff0c;但心里却在嘀咕“Ela…

作者头像 李华
网站建设 2026/8/27 13:48:45

跨平台开发效率提升:交叉编译最佳实践总结

跨平台开发效率提升&#xff1a;交叉编译实战指南与工程避坑全解析 你有没有经历过这样的场景&#xff1f; 在一块ARM开发板上跑 make 编译一个中等规模的C项目&#xff0c;风扇狂转、进度条爬得比蜗牛还慢——三小时后终于链接成功&#xff0c;结果运行时报错“非法指令”。…

作者头像 李华
网站建设 2026/8/27 13:47:50

系统缺少找不到d3d10.dll文件 如何下载修复问题?

在使用电脑系统时经常会出现丢失找不到某些文件的情况&#xff0c;由于很多常用软件都是采用 Microsoft Visual Studio 编写的&#xff0c;所以这类软件的运行需要依赖微软Visual C运行库&#xff0c;比如像 QQ、迅雷、Adobe 软件等等&#xff0c;如果没有安装VC运行库或者安装…

作者头像 李华
网站建设 2026/8/28 21:08:05

PaddlePaddle镜像与Spark整合进行大规模特征工程尝试

PaddlePaddle镜像与Spark整合进行大规模特征工程尝试 在推荐系统、广告点击率预估和内容理解等工业级AI应用中&#xff0c;一个常被低估但至关重要的现实是&#xff1a;80%的时间花在数据准备上&#xff0c;而只有20%用于模型训练本身。当企业面对每天TB级的用户行为日志时&…

作者头像 李华