news 2026/4/18 11:24:46

设计模式学习(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/4/18 8:19:04

OpenBMC平台构建完整指南:Yocto项目实战详解

手把手教你构建 OpenBMC&#xff1a;从零开始的 Yocto 实战之路你有没有遇到过这样的场景&#xff1f;服务器突然宕机&#xff0c;远程无法登录&#xff0c;KVM 连不上&#xff0c;只能派人去机房“拍电源键”——这种传统运维方式在现代数据中心早已不合时宜。而真正高效的解决…

作者头像 李华
网站建设 2026/4/14 19:35:32

d3dcompiler_43.dll文件找不到 无法运行程序 下载修复方法

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

作者头像 李华
网站建设 2026/4/16 4:46:31

中断下半部:延迟工作实验

一、该驱动程序的功能这个驱动程序是基于 Linux 中断下半部的 “延迟工作” 机制&#xff0c;实现按键中断的消抖处理&#xff1a;当按键触发中断时&#xff0c;不会立即处理按键事件&#xff0c;而是调度一个 “延迟工作”&#xff08;延迟指定时间&#xff0c;避开按键抖动的…

作者头像 李华
网站建设 2026/4/18 7:57:11

手把手教你排查Raspberry Pi上spidev0.0 read255

当SPI读出全是0xFF&#xff1f;别慌&#xff0c;带你一步步揪出Raspberry Pi上 spidev0.0 read255 的真凶 你有没有遇到过这种情况&#xff1a;在树莓派上用C通过 /dev/spidev0.0 读取一个SPI传感器&#xff0c;结果每次收到的数据都是 0xFF &#xff08;也就是十进制255…

作者头像 李华
网站建设 2026/4/18 7:41:07

LED阵列汉字显示实验在公交站牌中的实战案例

从实验室到街头&#xff1a;一场关于LED汉字显示的硬核实战你有没有在等公交时&#xff0c;盯着站牌上那条缓慢滚动的红色文字发过呆&#xff1f;“15路 还有2分钟到达”——简单几个字&#xff0c;背后却是一整套嵌入式系统在默默运行。这看似普通的显示效果&#xff0c;其实正…

作者头像 李华
网站建设 2026/4/18 7:29:02

PaddlePaddle批量处理折扣:大批量任务费用优化

PaddlePaddle批量处理折扣&#xff1a;大批量任务费用优化 在AI工业化落地的今天&#xff0c;一个现实问题正摆在企业面前&#xff1a;如何用有限的算力预算&#xff0c;支撑动辄百万级图像识别、文本解析或目标检测任务&#xff1f;尤其是在中文OCR、电商内容审核、金融票据处…

作者头像 李华