JAVA设计模式之原型模式详解

原型模式

1 原型模式介绍

定义: 原型模式(Prototype Design Pattern)用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

西游记中的孙悟空 拔毛变小猴,孙悟空这种根据自己的形状复制出多个身外化身的技巧,在面向对象软件设计领域被称为原型模式.孙悟空就是原型对象.

在这里插入图片描述

原型模式主要解决的问题

  • 如果创建对象的成本比较大,比如对象中的数据是经过复杂计算才能得到,或者需要从RPC接口或者数据库等比较慢的IO中获取,这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作.

2 原型模式原理

原型模式包含如下角色:

  • 抽象原型类(Prototype):它是声明克隆方法的接口,是所有具体原型类的公共父类,它可以是抽象类也可以是接口.
  • 具体原型类(ConcretePrototype):实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象.
  • 客户类(Client):在客户类中,让一个原型对象克隆自身从而创建一个新的对象.由于客户类针对抽象原型类Prototype编程.因此用户可以根据需要选择具体原型类,系统具有较好的扩展性,增加或者替换具体原型类都比较方便.

在这里插入图片描述

3 深克隆与浅克隆

根据在复制原型对象的同时是否复制包含在原型对象中引用类型的成员变量 这个条件,原型模式的克隆机制分为两种,即浅克隆(Shallow Clone)和深克隆(Deep Clone)

  1. 什么是浅克隆

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象(克隆对象与原型对象共享引用数据类型变量)。

在这里插入图片描述

  1. 什么是深克隆

除去那些引用其他对象的变量,被复制对象的所有变量都含有与原来的对象相同的值。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

在这里插入图片描述

Java中的Object类中提供了 clone() 方法来实现浅克隆。需要注意的是要想实现克隆的Java类必须实现一个标识接口 Cloneable ,来表示这个Java类支持被复制.

Cloneable接口是上面的类图中的抽象原型类,而实现了Cloneable接口的子实现类就是具体的原型类。代码如下:

  1. 浅克隆代码实现:
public class ConcretePrototype implements Cloneable {public ConcretePrototype() {System.out.println("具体的原型对象创建完成!");}@Overrideprotected ConcretePrototype clone() throws CloneNotSupportedException {System.out.println("具体的原型对象复制成功!");return (ConcretePrototype)super.clone();}
}

测试

    @Testpublic void test01() throws CloneNotSupportedException {ConcretePrototype c1 = new ConcretePrototype();ConcretePrototype c2 = c1.clone();System.out.println("对象c1和c2是同一个对象?" + (c1 == c2));}
  1. 深克隆代码实现

在ConcretePrototype类中添加一个对象属性为Person类型

public class ConcretePrototype implements Cloneable {private Person person;public Person getPerson() {return person;}public void setPerson(Person person) {this.person = person;}void show(){System.out.println("嫌疑人姓名: " +person.getName());}public ConcretePrototype() {System.out.println("具体的原型对象创建完成!");}@Overrideprotected ConcretePrototype clone() throws CloneNotSupportedException {System.out.println("具体的原型对象复制成功!");return (ConcretePrototype)super.clone();}}public class Person {private String name;public Person() {}public Person(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}

测试

    @Testpublic void test02() throws CloneNotSupportedException {ConcretePrototype c1 = new ConcretePrototype();Person p1 = new Person();c1.setPerson(p1);//复制c1ConcretePrototype c2 = c1.clone();//获取复制对象c2中的Person对象Person p2 = c2.getPerson();p2.setName("峰哥");//判断p1与p2是否是同一对象System.out.println("p1和p2是同一个对象?" + (p1 == p2));c1.show();c2.show();}

打印结果

在这里插入图片描述

说明: p1与p2是同一对象,这是浅克隆的效果,也就是对具体原型类中的引用数据类型的属性进行引用的复制.

如果有需求场景中不允许共享同一对象,那么就需要使用深拷贝,如果想要进行深拷贝需要使用到对象序列化流 (对象序列化之后,再进行反序列化获取到的是不同对象). 代码如下:

    @Testpublic void test03() throws Exception {ConcretePrototype c1 = new ConcretePrototype();Person p1 = new Person("峰哥");c1.setPerson(p1);//创建对象序列化输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("c.txt"));//将c1对象写到文件中oos.writeObject(c1);oos.close();//创建对象序列化输入流ObjectInputStream ois = new ObjectInputStream(new FileInputStream("c.txt"));//读取对象ConcretePrototype c2 = (ConcretePrototype) ois.readObject();Person p2 = c2.getPerson();p2.setName("凡哥");//判断p1与p2是否是同一个对象System.out.println("p1和p2是同一个对象?" + (p1 == p2));c1.show();c2.show();}

打印结果:

在这里插入图片描述

注意:ConcretePrototype类和Person类必须实现Serializable接口,否则会抛NotSerializableException异常。

其实现在不推荐大家用Cloneable接口,实现比较麻烦,现在借助Apache Commons或者springframework可以直接实现:

  • 浅克隆:BeanUtils.cloneBean(Object obj);BeanUtils.copyProperties(S,T);
  • 深克隆:SerializationUtils.clone(T object);

BeanUtils是利用反射原理获得所有类可见的属性和方法,然后复制到target类。
SerializationUtils.clone()就是使用我们的前面讲的序列化实现深克隆,当然你要把要克隆的类实现Serialization接口。

4 原型模式应用实例

模拟某银行电子账单系统的广告信发送功能,广告信的发送都是有一个模板的,从数据库查出客户的信息,然后放到模板中生成一份完整的邮件,然后交给发送机进行发送处理.

在这里插入图片描述

发送广告信邮件UML类图

在这里插入图片描述

代码实现

  • 广告模板代码
/*** 广告信模板代码**/
public class AdvTemplate {//广告信名称private String advSubject = "xx银行本月还款达标,可抽iPhone 13等好礼!";//广告信内容private String advContext = "达标用户请在2022年3月1日到2022年3月30参与抽奖......";public String getAdvSubject() {return this.advSubject;}public String getAdvContext() {return this.advContext;}
}
  • 邮件类代码
package com.demo.example01;/*** 邮件类**/
public class Mail {//收件人private String receiver;//邮件名称private String subject;//称谓private String appellation;//邮件内容private String context;//邮件尾部, 一般是"xxx版权所有"等信息private String tail;//构造函数public Mail(AdvTemplate advTemplate) {this.context = advTemplate.getAdvContext();this.subject = advTemplate.getAdvSubject();}public String getReceiver() {return receiver;}public void setReceiver(String receiver) {this.receiver = receiver;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getAppellation() {return appellation;}public void setAppellation(String appellation) {this.appellation = appellation;}public String getContext() {return context;}public void setContext(String context) {this.context = context;}public String getTail() {return tail;}public void setTail(String tail) {this.tail = tail;}
}
  • 客户类
/*** 业务场景类**/
public class Client {//发送信息的是数量,这个值可以从数据库获取private static int MAX_COUNT = 6;//发送邮件public static void sendMail(Mail mail){System.out.println("标题: " + mail.getSubject() + "\t收件人: " + mail.getReceiver()+ "\t..发送成功!");}public static void main(String[] args) {//模拟邮件发送int i = 0;//把模板定义出来,数据是从数据库获取的Mail mail = new Mail(new AdvTemplate());mail.setTail("xxx银行版权所有");while(i < MAX_COUNT){//下面是每封邮件不同的地方mail.setAppellation(" 先生 (女士)");Random random = new Random();int num = random.nextInt(9999999);mail.setReceiver(num+"@"+"liuliuqiu.com");//发送 邮件sendMail(mail);i++;}}
}
  • 运行结果

在这里插入图片描述

上面的代码存在的问题:

  • 发送邮件需要重复创建Mail类对象,而且Mail类的不同对象之间差别非常小,这样重复的创建操作十分的浪费资源.
  • 这种情况我们就可以使用原型模式,从其他已有的对象中进行拷贝,而不是每次都创建新对象,进行一些耗时的操作.

代码重构

  • Mail类
/*** 邮件类 实现Cloneable接口,表示该类的实例可以被复制**/
public class Mail implements Cloneable{//收件人private String receiver;//邮件名称private String subject;//称谓private String appellation;//邮件内容private String context;//邮件尾部, 一般是"xxx版权所有"等信息private String tail;//构造函数public Mail(AdvTemplate advTemplate) {this.context = advTemplate.getAdvContext();this.subject = advTemplate.getAdvSubject();}public String getReceiver() {return receiver;}public void setReceiver(String receiver) {this.receiver = receiver;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getAppellation() {return appellation;}public void setAppellation(String appellation) {this.appellation = appellation;}public String getContext() {return context;}public void setContext(String context) {this.context = context;}public String getTail() {return tail;}public void setTail(String tail) {this.tail = tail;}@Overridepublic Mail clone(){Mail mail = null;try {mail = (Mail)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return mail;}
}
  • Client类
/*** 业务场景类**/
public class Client {//发送信息的是数量,这个值可以从数据库获取private static int MAX_COUNT = 6;//发送邮件public static void sendMail(Mail mail){System.out.println("标题: " + mail.getSubject() + "\t收件人: " + mail.getReceiver()+ "\t..发送成功!");}public static void main(String[] args) {//模拟邮件发送int i = 0;//把模板定义出来,数据是从数据库获取的Mail mail = new Mail(new AdvTemplate());mail.setTail("xxx银行版权所有");while(i < MAX_COUNT){//下面是每封邮件不同的地方Mail cloneMail = mail.clone();cloneMail.setAppellation(" 先生 (女士)");Random random = new Random();int num = random.nextInt(9999999);cloneMail.setReceiver(num+"@"+"liuliuqiu.com");//发送 邮件sendMail(cloneMail);i++;}}
}

5 原型模式总结

原型模式的优点

  1. 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程, 通过复制一个已有实例可以提高新实例的创建效率.

    比如,在 AI 系统中,我们经常需要频繁使用大量不同分类的数据模型文件,在对这一类文件建立对象模型时,不仅会长时间占用 IO 读写资源,还会消耗大量 CPU 运算资源,如果频繁创建模型对象,就会很容易造成服务器 CPU 被打满而导致系统宕机。通过原型模式我们可以很容易地解决这个问题,当我们完成对象的第一次初始化后,新创建的对象便使用对象拷贝(在内存中进行二进制流的拷贝),虽然拷贝也会消耗一定资源,但是相比初始化的外部读写和运算来说,内存拷贝消耗会小很多,而且速度快很多

  2. 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构(具体工厂对应具体产品),而原型模式就不需要这样,原型模式的产品复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品.

  3. 可以使用深克隆的方式保存对象状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用,比如恢复到某一历史状态,可以辅助实现撤销操作.

    在某些需要保存历史状态的场景中,比如,聊天消息、上线发布流程、需要撤销操作的程序等,原型模式能快速地复制现有对象的状态并留存副本,方便快速地回滚到上一次保存或最初的状态,避免因网络延迟、误操作等原因而造成数据的不可恢复。

原型模式缺点

  • 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时需要修改源代码,违背了开闭原则.

使用场景

原型模式常见的使用场景有以下六种。

  • 资源优化场景。也就是当进行对象初始化需要使用很多外部资源时,比如,IO 资源、数据文件、CPU、网络和内存等。

  • 复杂的依赖场景。 比如,F 对象的创建依赖 A,A 又依赖 B,B 又依赖 C……于是创建过程是一连串对象的 get 和 set。

  • 性能和安全要求的场景。 比如,同一个用户在一个会话周期里,可能会反复登录平台或使用某些受限的功能,每一次访问请求都会访问授权服务器进行授权,但如果每次都通过 new 产生一个对象会非常烦琐,这时则可以使用原型模式。

  • 同一个对象可能被多个修改者使用的场景。 比如,一个商品对象需要提供给物流、会员、订单等多个服务访问,而且各个调用者可能都需要修改其值时,就可以考虑使用原型模式。

  • 需要保存原始对象状态的场景。 比如,记录历史操作的场景中,就可以通过原型模式快速保存记录。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2776072.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯省赛模板构建——uart

打开CubeMX 串口的发送是跟调试器放一起的&#xff0c;通过PA9和PA10来接收发送 选择异步通讯 波特率配置为9600 打开串口中断&#xff0c;因为单片机接收数据需要用到中断 生成代码 添加底层驱动代码 打开在main.h打开uart定义 uart时钟配置&#xff0c;由于uart是用PCLK时钟…

html5+css3胶囊按钮代码

效果 代码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <title></title> <style> /* 胶囊开关的样式 */ .switch { position: relative; display: inline-block; width: 6…

【c++入门】母牛生小牛

说明 有一头小母牛&#xff0c;从出生第四年起每年生一头小母牛&#xff0c;按此规律&#xff0c;第N年时有几头母牛&#xff1f; 输入数据 只有一个整数N&#xff0c;独占一行。(1≤N≤50) 输出数据 对每组数据&#xff0c;输出一个整数&#xff08;独占一行&#xff09;…

【SpringBootStarter】自定义全局加解密组件

【SpringBootStarter】 目的 了解SpringBoot Starter相关概念以及开发流程实现自定义SpringBoot Starter(全局加解密)了解测试流程优化 最终引用的效果&#xff1a; <dependency><groupId>com.xbhog</groupId><artifactId>globalValidation-spring…

猫头虎分享已解决Bug || 缓存溢出解决方案:CacheOverflowException 或 CacheOutOfMemoryError

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

6、5 门关于 AI 和 ChatGPT 的免费课程,带您从 0-100

5 门关于 AI 和 ChatGPT 的免费课程,带您从 0-100 想在 2024 年免费了解有关 AI 和 ChatGPT 的更多信息吗? 图片由 DALLE 3 提供 活着是多么美好的时光啊。还有什么比现在更适合了解生成式人工智能(尤其是 ChatGPT)等人工智能元素的呢!许多人对这个行业感兴趣,但有些…

函数及函数的定义

前言&#xff1a; 在之前介绍指针的时候&#xff0c;小编发现有些地方需要用函数&#xff0c;所以小编决定先带领大家学习函数&#xff0c;然后再学习指针。 函数是从英文function翻译过来的&#xff0c;其实function在英文中的意思就是函数&#xff0c;也是功能的意思&#xf…

Uniapp真机调试:手机端访问电脑端的后端接口解决

Uniapp真机调试&#xff1a;手机端访问电脑端的后端接口解决 1、前置操作 HBuilderX -> 运行 -> 运行到手机或模拟器 -> 运行到Android App基座 少了什么根据提示点击下载即可 使用数据线连接手机和电脑 手机端&#xff1a;打开开发者模式 -> USB调试打开手机端&…

Electron+Vue实现仿网易云音乐实战

前言 这个项目是我跟着官方文档的那个Electron入门教程大致跑了一遍,了解了下Electron开发流程之后的实战项目,所以中间应该是会有很多写法不是很规范,安全性有可能也没考虑到,可实现的各种api也不是很了解,适合初学者。 必须感谢 https://github.com/Binaryify/NeteaseC…

springboot微信小程序uniapp学习计划与日程管理系统

基于springboot学习计划与日程管理系统&#xff0c;确定学习计划小程序的目标&#xff0c;明确用户需求&#xff0c;学习计划小程序的主要功能是帮助用户制定学习计划&#xff0c;并跟踪学习进度。页面设计主要包括主页、计划学习页、个人中心页等&#xff0c;然后用户可以利用…

MySQL篇----第十五篇

系列文章目录 文章目录 系列文章目录前言一、实践中如何优化 MySQL二、优化数据库的方法三、简单描述 MySQL 中,索引,主键,唯一索引,联合索引的区别,对数据库的性能有什么影响(从读写两方面)前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分…

Flink CDC 与 Kafka 集成:Snapshot 还是 Changelog?Upsert Kafka 还是 Kafka?

博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维…

ClickHouse的优缺点和应用场景

当业务场景需要一个大批量、快速的、可支持聚合运算的数据库&#xff0c;那么可选择ClickHouse。 选择ClickHouse 的原因&#xff1a; 记录类型类似于LOG&#xff0c;读取、运算远远大于写入操作选取有限列&#xff0c;对近千万条数据&#xff0c;快算的运算出结果。数据批量…

Linux开发:PAM1 介绍

PAM(Pluggable Authentication Modules )是Linux提供的一种通用的认证方式,他可以根据需要动态的加载认证模块,从而减少认证开发的工作量以及提供认证的灵活度。 1.PAM的框架 PAM的框架由一下几个部分构成 1)应用程序,即需要使用认证服务的程序,这些应用程序是使用抽象…

【机器学习笔记】机器学习基本概念

机器学习基本概念 文章目录 机器学习基本概念1 概述2 机器学习实验方法与原则2.1 平均指标 2.2 训练集、验证集与测试集2.3 随机重复实验2.4 K折交叉验证2.4 统计有效性检验 1 概述 什么是机器学习 —— 在某种任务上基于经验不断进步 T (Task)&#xff1a;需要解决什么任务 P(…

如何写一个其他人可以使用的GitHub Action

前言 在GitHub中&#xff0c;你肯定会使用GitHub Actions自动部署一个项目到GitHub Page上&#xff0c;在这个过程中总要使用workflows工作流&#xff0c;并在其中使用action&#xff0c;在这个使用的过程中&#xff0c;总会好奇怎么去写一个action呢&#xff0c;所以&#xff…

数字图像处理实验记录六(图像的傅里叶变换和频域处理)

前言&#xff1a; 一、基础知识 1&#xff0c;傅里叶变换是什么 傅里叶变换是一种线性积分变换&#xff0c;通俗来说&#xff0c;通过傅里叶变换就是把一段信号分解成若干个简谐波。 二、实验要求 1&#xff0e;产生一幅如图所示亮块图像f(x,y)&#xff08;256256 大小、…

Project2007下载安装教程,保姆级教程,附安装包和工具

前言 Project是一款项目管理软件&#xff0c;不仅可以快速、准确地创建项目计划&#xff0c;而且可以帮助项目经理实现项目进度、成本的控制、分析和预测&#xff0c;使项目工期大大缩短&#xff0c;资源得到有效利用&#xff0c;提高经济效益。软件设计目的在于协助专案经理发…

数据结构入门(1)数据结构介绍

目录 前言 1. 什么是数据结构&#xff1f; 2.什么是算法&#xff1f; 3.数据结构和算法的重要性 前言 本文将开始介绍计算机里的数据结构。 数据结构是指数据对象中元素之间的关系&#xff0c;以及对这些关系的操作。数据结构可以分为线性结构和非线性结构。 线性结构是…

【MySQL进阶之路】BufferPool底层设计(下)

欢迎关注公众号&#xff08;通过文章导读关注&#xff1a;【11来了】&#xff09;&#xff0c;及时收到 AI 前沿项目工具及新技术的推送&#xff01; 在我后台回复 「资料」 可领取编程高频电子书&#xff01; 在我后台回复「面试」可领取硬核面试笔记&#xff01; 文章导读地址…