news 2026/4/23 16:03:54

设计模式学习(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/23 12:47:44

软路由实现带宽智能分配:实战配置示例

软路由如何让全家上网不打架&#xff1f;一文讲透带宽智能分配实战你有没有遇到过这样的场景&#xff1a;孩子在客厅刷4K视频&#xff0c;爸爸在书房开视频会议突然卡成PPT&#xff1b;或者自己打游戏正到关键时刻&#xff0c;队友语音断断续续听不清。而当你打开下载工具开始“…

作者头像 李华
网站建设 2026/4/23 12:16:04

PaddlePaddle镜像在医疗影像分析中的成功案例分享

PaddlePaddle镜像在医疗影像分析中的成功实践 在一家三甲医院的放射科&#xff0c;医生每天要阅上百张胸部X光片。面对疫情高峰期激增的筛查需求&#xff0c;人工判读不仅耗时费力&#xff0c;还容易因疲劳导致轻微病灶漏诊。而就在几个月前&#xff0c;这套基于国产AI平台构建…

作者头像 李华
网站建设 2026/4/23 12:10:18

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

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

作者头像 李华
网站建设 2026/4/23 10:19:30

Cordova与OpenHarmony数据备份恢复系统

欢迎大家加入开源鸿蒙跨平台开发者社区&#xff0c;一起共建开源鸿蒙跨平台生态。 数据备份系统概述 数据备份系统为用户提供了数据安全保护。在Cordova框架与OpenHarmony系统的结合下&#xff0c;我们需要实现一个完整的备份恢复系统&#xff0c;包括本地备份、云备份和恢复功…

作者头像 李华
网站建设 2026/4/23 13:57:28

《深入浅出python机器学习》读书笔记(十)——数据表达与特征工程2

文章目录自动特征选择使用单一变量法进行特征选择基于模型的特征选择迭代式特征选择&#x1f4ca; 结果解读与对比自动特征选择 在复杂数据集中&#xff0c;各特征对模型预测结果的重要性往往不同&#xff0c;有些特征贡献显著&#xff0c;有些则影响较弱。本文将介绍如何利用…

作者头像 李华
网站建设 2026/4/23 12:10:56

Arduino安装教程:USB转串驱动兼容性详解

Arduino开发避坑指南&#xff1a;USB转串芯片驱动兼容性实战解析 你有没有遇到过这样的情况&#xff1f;刚买回来一块Arduino Nano&#xff0c;兴冲冲打开IDE准备烧个“Blink”试试&#xff0c;结果上传失败、端口灰显、设备管理器里还挂着个黄色感叹号。别急——这大概率不是…

作者头像 李华