【设计模式】使用原型模式完成业务中“各种O”的转换
文章目录
- 1.原型模式概述
- 2.浅拷贝与深拷贝
- 2.1.浅拷贝的实现方式
- 2.2.深拷贝的实现方式
- 3.结语
1.原型模式概述
原型模式是一种非常简单易懂的模型,在书上的定义是这样的:
Specify the kinds of objects to create using a prototypical instance,and create new objects by
copying this prototype.
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
通俗的讲,就是有一个现成的对象,我们通过复制或拷贝这个对象的方式来创建一个新的对象,这就是原型模式。
那么,我们为什么需要通过拷贝来创建对象呢?
我认为主要体现在两个维度:程序运行效率和开发效率
- 程序运行效率:如果对象的创建和初始化的代价比较大,创建比较繁琐,例如需要从数据库、RPC、网络等获取一些数据才能完成创建,这时候使用原型模式拷贝出一个对象无疑是效率更高的做法。
- 开发效率:我们在业务开发中,会涉及到不同的分层,每个分层都有自己的数据实例,例如与前端交互的
VO
对象,数据传输的DTO
对象,与数据库交互的PO
对象等等,这些对象转换的过程中大部分的字段值都是相同的,如果每一层都使用getter/setter
方法来进行赋值,开发效率就很容易受到影响,尤其是在迭代中增加了新字段的情况下,每一层都需要新增一个字段set
方法,很麻烦也很容易做漏,这时候使用原型模式来进行复制就比较方便了。
严格的说,原型模式是同一个类型下的不同实例的数据拷贝,对于不同类型的实例拷贝更应该归类于原型模式的一种拓展用法。不过在业务中,这种拓展用法使用的频率更高,接下来的示例也是以这种拓展用法为主。
2.浅拷贝与深拷贝
想要在使用原型模式的时候,不出现一些“意外”,那就得先了解浅拷贝与深拷贝之间的区别。
两者的区分非常好理解,关键点就是在对引用类型的成员变量拷贝上,两种拷贝类型有不同的结果:
- 浅拷贝:只会将原始对象中的引用类型变量的内存地址复制给目标对象的同名成员变量。
- 深拷贝:会以引用类型变量的类型为基础,创建一个崭新的对象,再把这个新对象的内存地址赋值给目标对象的对应同名成员变量。
对于浅拷贝来说,如果原始对象中有其他的引用类型变量,在拷贝出目标对象后,两个对象对于该引用类型变量的修改会互相影响,但是由于不需要针对引用类型的变量查询新的对象,这种拷贝方式的效率较高。在不会修改变量值或者只需要修改基本类型的变量值(包含String)时,优先考虑使用浅拷贝。
如果拷贝的对象中包含了引用类型的对象,且需要进行修改,或者拷贝不同类型但内部字段相同的对象(例如:UserPO和UserDTO),可以考虑使用深拷贝。
2.1.浅拷贝的实现方式
浅拷贝的实现方式有很多,归类起来主要是两种,一种是使用Java原生的方式,另一种是通过开源的工具包实现。
先看看Java原生的方式,实现一个Cloneable
接口,并覆写父类Object
中的clone
方法:
@Getter
@Setter
public class User implements Cloneable {private String name;private int age;private Phone phone;@Overridepublic User clone() throws CloneNotSupportedException {return (User) super.clone();}
}@Getter
@Setter
public class Phone {private String phoneNumber;
}
为了验证浅拷贝,这里还加入了一个Phone
对象,接下来就做个测试。
public static void main(String[] args) throws CloneNotSupportedException {User user = new User();user.setName("张三");user.setAge(18);user.setPhone(new Phone());User cloneUser = user.clone();System.out.println(user.getPhone() == cloneUser.getPhone());
}
打上一个断点,查看两个对象,可以看到User
对象已经成功复制,并且里面的Phone
对象明显是同一个对象,
使用开源的工具,常用的有两种工具分别是Apache commons
中的BeanUtils
或PropertyUtils
,Spring
中的BeanUtils
,他们都有一个共同的方法是copyProperties
用来拷贝对象属性。在实现中,虽然Apache
和Spring
都是通过反射来实现的,但是Spring
针对反射做了一层缓存,在相同类型的对象复制中,效率高于Apache
,所以我们选择使用Spring
的拷贝。
没有Spring
依赖的话,需要引入依赖包:
<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.9</version>
</dependency>
修改一下代码,可以得到一样的结果:
public static void main(String[] args) {User user = new User();user.setName("张三");user.setAge(18);user.setPhone(new Phone());User cloneUser = new User();BeanUtils.copyProperties(user, cloneUser);System.out.println(user.getPhone() == cloneUser.getPhone());
}
2.2.深拷贝的实现方式
深拷贝的实现有两种方式,一种是通过递归来拷贝,既然一次拷贝对于引用变量来说只能拷贝地址,那就再把引用变量也做一次拷贝就可以了,只是这种方式太麻烦了。我们一般会选择第二种方式,通过序列化与反序列化来完成对象的拷贝。
其实在我们常见的RPC
通信中,就是使用的这种方式来完成对象拷贝的,请求方将对象序列化成流、字节数组、JSON、XML等等方式来做传输,接收方收到数据后,用同样的方式反序列化成一个新的对象。我们也可以采用这种方式来完成对象的拷贝。
这里使用一个FastJson
工具类做了一个简单的封装,封装了两个方法,拷贝单个对象和拷贝List对象:
public class DeepCloneUtil {/*** 克隆单个对象** @param source 被克隆的源对象* @param targetClazz 克隆目标对象的类型* @param <T> 目标对象泛型* @return 克隆模板对象*/public static <T> T cloneObject(Object source, Class<T> targetClazz) {String jsonString = JSON.toJSONString(source);return JSON.parseObject(jsonString, targetClazz);}/*** 克隆List对象** @param source 被克隆的源对象* @param targetClazz 克隆目标对象的类型* @param <T> 目标对象泛型* @return 克隆模板对象*/public static <T> List<T> cloneList(List<?> source, Class<T> targetClazz) {String jsonString = JSON.toJSONString(source);return JSON.parseArray(jsonString, targetClazz);}}
修改一下测试代码:
public static void main(String[] args) {User user = new User();user.setName("张三");user.setAge(18);user.setPhone(new Phone());User cloneUser = DeepCloneUtil.cloneObject(user, User.class);System.out.println(user.getPhone() == cloneUser.getPhone());
}
此时,两个Phone
对象就不是同一个对象了,这样就完成了深拷贝。
再试试列表拷贝:
public static void main(String[] args) {User user = new User();user.setName("张三");user.setAge(18);user.setPhone(new Phone());User user1 = new User();user1.setName("李四");user1.setAge(19);user1.setPhone(new Phone());List<User> list = Arrays.asList(user, user1);List<User> cloneList = DeepCloneUtil.cloneList(list, User.class);}
可以看到不管是List,还是User,还是Phone,每一个都是不同的对象。
3.结语
本篇主要讲述的是原型模式的概念及其使用,并引出了深拷贝与浅拷贝的区别。
不管是从创建对象的性能上考虑,还是从开发效率上考虑,都可以在合适的时候选择使用原型模式拷贝对象的方式来替代从头开始创建一个新的对象。
相关文章:

【设计模式】使用原型模式完成业务中“各种O”的转换
文章目录 1.原型模式概述2.浅拷贝与深拷贝2.1.浅拷贝的实现方式2.2.深拷贝的实现方式 3.结语 1.原型模式概述 原型模式是一种非常简单易懂的模型,在书上的定义是这样的: Specify the kinds of objects to create using a prototypical instance,and cre…...
[C++ 网络协议] IOCP(Input Output Completion Port)
1.什么是IOCP IOCP(Input Output Completion Port)输入输出完成端口。其实就是基于重叠I/O的一种改进的模型。 重叠I/O具有缺点:重复调用非阻塞模式的accpet函数和以进入alertablewait状态为目的的SleepEx函数会影响程序性能。 而IOCP提供…...

R实现地图相关图形绘制
大家好,我是带我去滑雪! 地图相关图形绘制具有许多优点,这些优点使其在各种领域和应用中非常有用。例如:地图相关图形提供了一种直观的方式来可视化数据,使数据更容易理解和分析。通过地图,可以看到数据的空…...

【Jmeter】性能测试脚本开发——性能测试环境准备、Jmeter脚本编写和执行
文章目录 一、常用的Jmeter元件二、性能测试环境准备三、编写Jmeter脚本四、执行测试脚本 一、常用的Jmeter元件 取样器-HTTP请求 作用:发送HTTP请求配置原件-HTTP请求默认值 作用:设置HTTP请求的默认参数配置原件-用户定义的变量 作用:定义…...

看好你家电视盒的后门!数千个Android电视盒感染了与欺诈相关的危险恶意软件
如果你从Android电视盒获得流媒体修复程序,则你的设备可能会被恶意软件所感染,这些恶意软件能够进行广告欺诈、创建假帐户,并通过悄悄地将你的数据转移到中国的服务器来销售对家庭网络的访问。 根据本周的一份新报告,网络安全公司…...

LeetCode 1251. 平均售价
题目链接:1251. 平均售价 题目描述 表:Prices Column NameTypeproduct_idintstart_datedateend_datedatepriceint (product_id,start_date,end_date) 是 prices 表的主键(具有唯一值的列的组合)。 price…...

TypeScript 笔记:String 字符串
1 对象属性 length 返回字符串的长度 2 对象方法 charAt() 返回在指定位置的字符 charCodeAt() 返回在指定的位置的字符的 Unicode 编码 concat 连接两个或更多的字符串 indexOf 返回某个指定的字符串值在字符串中首次出现的位置 lastIndexOf 从后向前搜索字符串&…...

蓝牙技术|Matter或能改变中国智能家居市场,蓝牙技术将得到进一步应用
近年来,智能家居开放协议标准Matter(目前版本 1.1)由连接标准联盟发布,该联盟是一个由数百家公司组成的全球性机构,旨在提供与物联网 (IoT) 相关的标准。例如,Matter 用于允许 Amazon Alexa、Apple Home、G…...

VB.NET vs. VB6.0:现代化编程语言 VS 经典老旧语言
目录 .NET背景: 特点: VB6.0背景: 特点: 两者之间的不同: 总结: 升华: .NET背景: VB.NET一种简单,现代,面向对象计算机编程语言,有微软开发,VB.NET是一种基于.NET Framework的面向对象…...

ViewPager、RecycleView实现轮播图
1.ViewPager实现轮播图形效果。 1)layout中,PageIndicatorView轮播的View <RelativeLayoutandroid:layout_width"match_parent"android:layout_height"200dp"android:orientation"vertical"><androidx.viewpager…...

【FreeRTOS】【STM32】01从零开始的freertos之旅 浏览源码下的文件夹
基于野火以及正点原子 在打开正点原子的资料pdf时,我遇到了pdf无法复制粘贴的问题,这里有个pdf解锁文字复制功能的网址,mark一下。超级pdf 参考资料《STM32F429FreeRTOS开发手册_V1.2》 官方资料 FreeRTOS 的源码和相应的官方书籍均可从官…...

【PPT】ppt里面使用svg图标
要想编辑好的PPT,少不了小图标的美化,图标可以使PPT变得更有趣,更易懂,更美观。 对于png,主要处理它的颜色,可使用【重新着色】功能。 对于jpg,主要处理它的背景,删除背景后同png处…...

uni-app:实现页面效果4(echarts数据可视化)
效果 代码 <template><view><view><view class"title">概况</view><view class"line_position"><view class"line1"><view class"item"><view class"one">今日销售…...

vue实现echarts中 9种 折线图图例
let datas [{ DivideScore: 7, UserScore: 7.2, Name: 目标制定 },{ DivideScore: 7, UserScore: 7, Name: 具体性 },{ DivideScore: 7, UserScore: 7.5, Name: 可衡量性 },{ DivideScore: 7, UserScore: 7, Name: 可实现性 },{ DivideScore: 7, UserScore: 7, Name: 时间限定…...

redis实战-实现用户签到UV统计
BitMap功能演示 我们针对签到功能完全可以通过mysql来完成,比如说以下这张表 用户一次签到,就是一条记录,假如有1000万用户,平均每人每年签到次数为10次,则这张表一年的数据量为 1亿条 每签到一次需要使用(…...
作为创始人的价值观与心法,构建系统
价值观 绿色、健康、有趣、感恩、谦卑、责任、勇气、客观、冷静、自洽、尊重、价值、服务、善良、利他 作为co-founder衡量的一个很重要的标准就是这个人的人品,大家一起做事情的体验要好,才能有large energy,且流通。 乐观、通达…...
Go语言基础面经
1.go语言编程的好处是什么 编译和运行都很快。 在语言层级支持并行操作。 有垃圾处理器。 内置字符串和 maps。 函数是 go 语言的最基本编程单位。 2.说说go语言的select机制 select 机制用来处理异步 IO 问题 select 机制最大的一条限制就是每个 case 语句里必须是一个…...

服务器文件备份
服务器上,做好跟应用程序有关的文件备份(一般备份到远程的盘符),有助于当服务器发生硬件等故障时,可以对系统进行进行快速恢复。 下面以Windows服务器为例,记录如何做文件的备份操作。 具体操作如下&#…...

剑指offer——JZ68 二叉搜索树的最近公共祖先 解题思路与具体代码【C++】
一、题目描述与要求 二叉搜索树的最近公共祖先_牛客题霸_牛客网 (nowcoder.com) 题目描述 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 1.对于该题的最近的公共祖先定义:对于有根树T的两个节点p、q,最近公共祖先LCA(T,p,q)表示一个节点x&#…...

[Spring] @Bean 修饰方法时如何注入参数
目录 一、Bean 的简单使用 1、正常情况 2、问题提出 二、解决方案 1、Qualifier 2、直接写方法名 三、特殊情况 1、DataSource 一、Bean 的简单使用 在开发中,基于 XML 文件配置 Bean 对象的做法非常繁琐且不好维护,因此绝大部分情况下都是使用…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

MongoDB学习和应用(高效的非关系型数据库)
一丶 MongoDB简介 对于社交类软件的功能,我们需要对它的功能特点进行分析: 数据量会随着用户数增大而增大读多写少价值较低非好友看不到其动态信息地理位置的查询… 针对以上特点进行分析各大存储工具: mysql:关系型数据库&am…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...

计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...