【设计模式】使用原型模式完成业务中“各种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 对象的做法非常繁琐且不好维护,因此绝大部分情况下都是使用…...
wordpress后台更新后 前端没变化的解决方法
使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
Angular微前端架构:Module Federation + ngx-build-plus (Webpack)
以下是一个完整的 Angular 微前端示例,其中使用的是 Module Federation 和 npx-build-plus 实现了主应用(Shell)与子应用(Remote)的集成。 🛠️ 项目结构 angular-mf/ ├── shell-app/ # 主应用&…...
