news 2026/5/16 20:46:25

设计模式学习(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/5/12 5:28:55

PaddleGAN图像生成实战:使用PaddlePaddle镜像训练StyleGAN2模型

PaddleGAN图像生成实战&#xff1a;使用PaddlePaddle镜像训练StyleGAN2模型 在AI内容生成热潮席卷全球的今天&#xff0c;高质量图像生成已不再是实验室里的“黑科技”&#xff0c;而是越来越多地出现在数字人、虚拟偶像、广告创意乃至影视特效的实际产线中。然而&#xff0c;对…

作者头像 李华
网站建设 2026/5/10 1:26:34

PaddlePaddle镜像安全加固策略:保障企业AI开发环境稳定

PaddlePaddle镜像安全加固策略&#xff1a;保障企业AI开发环境稳定 在金融、制造和政务等关键行业加速推进智能化转型的今天&#xff0c;AI开发环境的安全性正面临前所未有的挑战。一个看似普通的容器镜像&#xff0c;可能隐藏着足以让整个训练集群陷入瘫痪的漏洞。某大型银行…

作者头像 李华
网站建设 2026/5/9 11:52:32

PaddlePaddle镜像自动化脚本分享:一键安装+测试+部署

PaddlePaddle镜像自动化脚本实践&#xff1a;从零到一键部署 在AI项目落地的过程中&#xff0c;你是否也经历过这样的场景&#xff1f;新同事刚入职&#xff0c;花了一整天配置环境——Python版本不对、CUDA驱动不匹配、paddlepaddle安装报错……最后发现“代码跑不通”只是因为…

作者头像 李华
网站建设 2026/5/7 4:23:23

PaddlePaddle镜像部署后无法访问GPU?排查思路全记录

PaddlePaddle镜像部署后无法访问GPU&#xff1f;排查思路全记录 在深度学习项目从开发走向生产的落地过程中&#xff0c;一个看似简单却频繁发生的“低级错误”——容器里跑不起来GPU&#xff0c;常常让开发者耗费数小时甚至一整天去排查。尤其是使用国产主流框架 PaddlePaddl…

作者头像 李华
网站建设 2026/5/3 12:24:15

PD多口适配器:多设备时代的充电效率革命

在智能手机、平板电脑、笔记本电脑、智能手表等设备高度普及的今天&#xff0c;用户常面临"充电接口不够用"的痛点。PD多口适配器凭借其高功率输出、智能功率分配和广泛兼容性&#xff0c;成为解决多设备充电难题的核心方案。本文将深度解析PD多口适配器的技术原理、…

作者头像 李华
网站建设 2026/5/1 8:21:03

PaddlePaddle镜像与HuggingFace生态能否打通?技术路径分析

PaddlePaddle与HuggingFace生态能否打通&#xff1f;一场关于模型互操作的深度探索 在今天&#xff0c;一个AI工程师的日常可能是在HuggingFace上挑选最新的多语言BERT变体&#xff0c;准备用于某个中文信息抽取项目。但当他试图将模型部署到生产环境时&#xff0c;却发现目标平…

作者头像 李华