【设计模式】使用原型模式完成业务中“各种O”的转换

news2024/11/28 21:38:14

文章目录

  • 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;

    @Override
    public 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中的BeanUtilsPropertyUtilsSpring中的BeanUtils,他们都有一个共同的方法是copyProperties用来拷贝对象属性。在实现中,虽然ApacheSpring都是通过反射来实现的,但是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.结语

本篇主要讲述的是原型模式的概念及其使用,并引出了深拷贝与浅拷贝的区别。
不管是从创建对象的性能上考虑,还是从开发效率上考虑,都可以在合适的时候选择使用原型模式拷贝对象的方式来替代从头开始创建一个新的对象。

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

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

相关文章

RobotFramework 自动化测试实战基础篇

RobotFramework 简介和特点 RobotFramework 不是一个测试工具&#xff0c;准确来说&#xff0c;它是一个自动化测试框架&#xff0c;或者说它是一个自动化测试平台。他拥有的特性如下&#xff1a; 支持关键字驱动、数据驱动和行为驱动测试执行报告和日志是HTML格式&#xff0…

并发编程基础知识

一、线程的基础概念 一、基础概念 1.1 进程与线程A 什么是进程&#xff1f; 进程是指运行中的程序。 比如我们使用钉钉&#xff0c;浏览器&#xff0c;需要启动这个程序&#xff0c;操作系统会给这个程序分配一定的资源&#xff08;占用内存资源&#xff09;。 什么线程&a…

android 修改输出apk的包名

一&#xff0c;打包方式使用IDE菜单选项 二、在app级别的build.gradle下配置&#xff1a; static def releaseTime() {return new Date().format("yyyyMMdd.kkmm", TimeZone.getTimeZone("GMT8")) }android.applicationVariants.all { variant ->print…

滚珠螺母的生产流程

滚珠螺母是机械领域中重要的零部件之一&#xff0c;它的生产过程涉及多个环节和步骤。以下是一个概括性的滚珠螺母生产流程&#xff1a; 1、原料采购&#xff1a;首先需要采购适合制造滚珠螺母的原材料&#xff0c;如钢棒、钢板等。 2、钢材切割&#xff1a;将采购的钢棒、钢板…

BLIP2模型加载在不同设备上

背景 现在大语言模型越来越大&#xff0c;占用的内存越来越多&#xff0c;这导致内存较小的设备无法体验大模型的效果。transformer提供了将一个大模型分别加载在gpu和cpu上的方法。 加载方法 以多模态模型BLIP2为例&#xff0c;将其语言模型放在gpu上&#xff0c;其余部分放…

1.10.C++项目:仿muduo库实现并发服务器之Acceptor模块的设计

一、Acceptor模块&#xff1a;这是一个对于通信连接进行整体管理的一个模块&#xff0c;对一个连接的操作都是通过这个模块来进行&#xff01; 二、提供的功能 Acceptor模块是对Socket模块&#xff0c;Channel模块的⼀个整体封装&#xff0c;实现了对⼀个监听套接字的整体的管…

中国人民大学与加拿大女王大学金融硕士-----成功便是站起来比倒下多一次

人生说短也短&#xff0c;说长也长。在有限的时间内&#xff0c;让我们按下“加速键”&#xff0c;收获生活或工作中的各种美好。人生的每一次加速&#xff0c;都距未来更近一步。身处金融领域的你&#xff0c;有想到比别人更快一步的拿到学位吗&#xff1f;中国人民大学与加拿…

yolov5加关键点回归

文章目录 一、数据1&#xff09;数据准备2&#xff09;标注文件说明 二、基于yolov5-face 修改自己的yolov5加关键点回归1、dataloader,py2、augmentations.py3、loss.py4、yolo.py 一、数据 1&#xff09;数据准备 1、手动创建文件夹: yolov5-face-master/data/widerface/tr…

K8S:K8S对外服务之Ingress

文章目录 一.Ingress基础介绍1.Ingress概念2.K8S对外暴露服务&#xff08;service&#xff09;主要方式&#xff08;1&#xff09;NodePort&#xff08;2&#xff09;LoadBalancer&#xff08;3&#xff09;externalIPs&#xff08;4&#xff09;Ingress 3.Ingress 组成&#x…

JS进阶-原型

原型 原型就是一个对象&#xff0c;也称为原型对象 构造函数通过原型分配的函数是所有对象所共享的 JavaScript规定&#xff0c;每一个构造函数都有一个prototype属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象 这个对象可以挂载函数&#xff0c;对象实…

8 个 Promise 高级用法

在 js 项目中&#xff0c;promise 的使用应该是必不可少的&#xff0c;但我发现在同事和面试者中&#xff0c;很多中级或以上的前端都还停留在promiseInst.then()、promiseInst.catch()、Promise.all等常规用法&#xff0c;连async/await也只是知其然&#xff0c;而不知其所以然…

Vue3实现div拖拽改变宽高

效果图如下&#xff1a; 底部拖拽按钮点击拖拽可自定义父容器的宽高 <template><div id"business_plane"><div class"business_plane" ref"container"><div class"darg_tool"><el-icon class"drag_H…

JavaEE初阶学习:HTTP协议和Tomcat

1. HTTP协议 HTTP协议是一个非常广泛的应用层协议~~ 应用层协议 —> TCP IP 协议栈 应用层 —> 关注数据怎么使用~ 传输层 —> 关注的是整个传输的起点和终点 网络层 —> 地址管理 路由选择 数据链路层 —> 相邻节点之间的数据转发 物理层 —> 基础设置,硬…

Rocky(centos)安装nginx并设置开机自启

一、安装nginx 1、安装依赖 yum install -y gcc-c pcre pcre-devel zlib zlib-devel openssl openssl-devel 2、去官网下载最新的稳定版nginx nginx: downloadhttp://nginx.org/en/download.html 3、将下载后的nginx上传至/usr/local下 或者执行 #2023-10-8更新 cd /usr/…

我在 NPM 发布了新包: con-colors

链接地址&#xff1a;npmjs.com con-colors 安装依赖 yarn add con-colors使用 导入&#xff1a; import { print } from "con-colors";使用&#xff1a; print.succ("成功的消息"); print.err("失败的消息")例子&#xff1a; import { p…

与诈蟹的初次邂逅,你中招了没

中秋国庆双节大家都过得怎么样&#xff1f;有没有吃到螃蟹&#xff1f;不管你们吃没吃到&#xff0c;反正东东是吃到螃蟹...的瓜了&#xff0c;四舍五入一下也算是吃到了吧。 这不节后上班第一天&#xff0c;同事们就已经开始互相问候关于是否收到蟹卡的情况&#xff0c;一开始…

vmware一键启动虚拟机系统脚本

bat脚本 "D:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" -T ws start "D:\Program Files (x86)\Ginkgo7000\C7-10.10.10.111\CentOS 7-NAS-6.vmx" 脚本内容说明 "D:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"…

一般香港服务器带宽选多大够用?(带宽计算方法)

​  在海外IDC市场份额中&#xff0c;香港服务器依托自身优越的服务器资源条件&#xff0c;在各个行业中发挥的重要作用。但是&#xff0c;不同业务对网络带宽的要求各不相同&#xff0c;弄清楚如何计算带宽需求对于确保业务平稳运行至关重要&#xff0c;最好从一开始就使用正…

研发质量管理体系

研发质量管理体系的脉络是怎样的&#xff1f;如何建立适合组织发展的研发质量管理体系&#xff1f;质量管理的核心是什么&#xff1f;一些思考&#xff0c;一些线索&#xff0c;欢迎朋友们一起探讨、碰撞。

Tensorflow入门之 Hello World

Tensorflow入门之 Hello World 简介 Tensorflow 是 Google 开源的深度学习框架&#xff0c;来自于 Google Brain 研究项目&#xff0c;在 Google 第一代分布式机器学习框架 DistBelief 的基础上发展起来。 Tensorflow 的官方网址 http://www.tensorflow.org Tensorflow 的 G…