当前位置: 首页 > news >正文

重修设计模式-创建型-原型模式

重修设计模式-创建型-原型模式

原型模式就是利用已有对象(原型)通过拷贝方式来创建对象的模式,达到节省对象创建时间的目的。适用于对象创建成本较大,且同一类的不同对象之间差别不大的场景。

比如一个对象中的数据需要经过复杂计算才能得到(如排序),或者对象是从网络、文件系统等通过IO读取的,这种情况下就可以用原型模式快速拷贝出一个新对象来使用,而不是再经过复杂计算或读取 IO 来创建对象。

原型模式的核心就是对象的拷贝,且有浅拷贝和深拷贝的区别。

  • 浅拷贝:只会复制基本类型的数据和引用对象的内存地址,不会递归的拷贝引用对象本身,比如 Java 中 Object 的 clonse() 方法。
  • 深拷贝:不仅复制基本类型的数据,也会拷贝引用类型的对象(会开辟新的内存空间,并将新开辟的内存地址引用给新对象),从而得到一份完全独立的对象。

Kotlin 中 data class 的 copy() 方法,或 Java 中 Object 的 clone() 方法都是浅拷贝的实现,下面验证一下:

data class User(var name: String, var age: Int, val address: Address): Cloneable {public override fun clone(): Any {return super.clone()}
}data class Address(var street: String, var city: String) : Cloneable {override fun clone(): Any {return super.clone()}
}

测试 copy() 和 clone() 方法:

fun main(args: Array<String>) {val user = User("白泽", 18, Address("浦东新区花园石桥路28弄", "上海"))val userCopy = user.copy()val userCopy1 = user.clone() as Userprintln("user1:${user}")println("user2:${userCopy}")println("user address:${user.address == userCopy.address}")     //判断值,相当于equalprintln("user address:${user.address === userCopy.address}")    //判断内存地址println("---")println("user1:${user}")println("user2:${userCopy1}")println("user address:${user.address == userCopy1.address}")     //判断值,相当于equalprintln("user address:${user.address === userCopy1.address}")    //判断内存地址
}

代码执行结果:

user1:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user2:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user address:true
user address:true
---
user1:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user2:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user address:true
user address:true

可见引用类型对象指向的内存地址还是同一个,并没有真正地进行拷贝,这一点通过源码也可以得到验证。将 User 编译成字节码后,再反编译成 Java 语言:

public final class User implements Cloneable {@NotNullprivate String name;private int age;@NotNullprivate final Address address;//调用的是Object的clone()方法@NotNullpublic Object clone() {return super.clone();}//componentX()方法,用于解构赋值语法@NotNullpublic final User copy(@NotNull String name, int age, @NotNull Address address) {Intrinsics.checkNotNullParameter(name, "name");Intrinsics.checkNotNullParameter(address, "address");return new User(name, age, address);}
}
public final class Address implements Cloneable {@NotNullprivate String street;@NotNullprivate String city;@NotNullpublic Object clone() {return super.clone();}@NotNullpublic final Address copy(@NotNull String street, @NotNull String city) {Intrinsics.checkNotNullParameter(street, "street");Intrinsics.checkNotNullParameter(city, "city");return new Address(street, city);}
}

可以看到,copy() 方法并不会对引用类型对象进行拷贝工作,而是直接传入。而 clone() 方法则是由底层实现,执行逻辑相同。

@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;

下面介绍深拷贝的几种实现方式:
1.深拷贝实现—自己实现Cloneable的clone方法:

data class User(var name: String, var age: Int, val address: Address): Cloneable {public override fun clone(): Any {return User(name, age, address.clone() as Address)}
}data class Address(var street: String, var city: String) : Cloneable {public override fun clone(): Any {return Address(street, city)}
}

打印结果:

user1:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user2:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user address:true
user address:false

引用地址不相同了,说明是两个独立的对象,这种方式比较麻烦,增减字段都需要对 clone() 方法进行维护,如果引用对象嵌套较深还容易出错。

2.深拷贝实现—序列化:

fun main(args: Array<String>) {val user = User("白泽", 18, Address("浦东新区花园石桥路28弄", "上海"))val userCopy = deepCopy(user) as Userprintln("user1:${user}")println("user2:${userCopy}")println("user address:${user.address == userCopy.address}")     //判断值,相当于equalprintln("user address:${user.address === userCopy.address}")    //判断内存地址
}fun deepCopy(obj: Any?): Any {val bo = ByteArrayOutputStream()val oo = ObjectOutputStream(bo)oo.writeObject(obj)val bi = ByteArrayInputStream(bo.toByteArray())val oi = ObjectInputStream(bi)return oi.readObject()
}

打印结果:

user1:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user2:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28弄, city=上海))
user address:true
user address:false

这种实现方式需要类中所有引用类型(包括嵌套的)都实现 Serializable 接口,否则会抛出 NotSerializableException 异常。

3.深拷贝实现—Json

fun main(args: Array<String>) {val user = User("白泽", 18, Address("浦东新区花园石桥路28弄", "上海"))val userCopy = deepCopy(user)println("user1:${user}")println("user2:${userCopy}")println("user address:${user.address == userCopy.address}")     //判断值,相当于equalprintln("user address:${user.address === userCopy.address}")    //判断内存地址
}inline fun <reified T> deepCopy(data: T): T {val gson = Gson()val json = gson.toJson(data)return gson.fromJson<T>(json, T::class.java)
}

打印结果:

user1:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28, city=上海))
user2:User(name=白泽, age=18, address=Address(street=浦东新区花园石桥路28, city=上海))
user address:true
user address:false

这里使用 Kotlin 的内联函数配合范型实化封装了 deepCopy 方法,其内部是通过Google 的 Json 解析库 Gson 进行实现的,先将对象转为 Json 字符串,然后再解析 Json 来创建新对象。

4.深拷贝实现—写时复制思想

以上深拷贝方式由于在拷贝时创建了大量对象,都会伴随性能损耗。其实可以借助 Copy On Write 思想,先通过浅拷贝将对象复制出来,再使用中再进行对象的创建,从而将对象创建的损耗平摊到后续业务中。

当然这种方式需要看具体业务场景再决定如何实现,如果应用一个复杂的模式,只得到一点点的性能提升,这就是所谓的过度设计,得不偿失。

经典场景

理论说完了,再结合实际场景来尝试使用。比如 App 的个人中心展示了用户的所有信息,如用户名,生日,地址,个性签名等,同时还有保存和重置按钮,其中保存按钮需要实时更新状态:只有真正的修改了用户信息才可以点击,否则置灰无法点击。重置按钮点击后会恢复页面原信息。

这种需求场景可以利用原型模式的思想,通过拷贝得到一个新对象,后续的信息修改都基于新对象,并且每次改动后都和原对象进行对比,判断字段值是否真正修改来更新保存按钮状态。在点击重置按钮时,重新基于原对象拷贝新对象,并根据该对象刷新页面实现重置功能。

总结

利用已有对象来创建新对象,以达到节省创建时间的目的,就叫做原型模式。

原型模式的核心是对象的拷贝,对象拷贝又分为浅拷贝和深拷贝,其中浅拷贝会共用引用类型对象,而深拷贝会创建新的引用对象。

相关文章:

重修设计模式-创建型-原型模式

重修设计模式-创建型-原型模式 原型模式就是利用已有对象&#xff08;原型&#xff09;通过拷贝方式来创建对象的模式&#xff0c;达到节省对象创建时间的目的。适用于对象创建成本较大&#xff0c;且同一类的不同对象之间差别不大的场景。 比如一个对象中的数据需要经过复杂…...

S71200 - 编程 - 笔记

1 DEMO 1.1气阀控制 1.2 红绿灯 基于PLC红绿灯控制_哔哩哔哩_bilibili 2 介绍变量DB&#xff0c;M&#xff0c;I&#xff0c;Q的使用 在PLC编程中&#xff0c;通常会使用多种类型的变量来实现逻辑控制、数据存储和输入输出操作。以下是常见的PLC变量类型及其用途&#xff…...

【项目】畅聊天地博客测试报告

项目简介&#xff1a;本项目采用 SSM框架结合 Websocket 技术构建。用户通过简单的注册和登录即可进入聊天室&#xff0c;与其他在线用户实时交流。系统支持文字消息的快速发送和接收、消息实时推送&#xff0c;确保交流的及时性和流畅性。SSM 框架为项目提供了稳定的架构和高效…...

【Next】全局样式和局部样式

不同于 nuxt &#xff0c;next 的样式绝大部分都需要手动导入。 全局样式 使用 sass 先安装 npm i sass -D 。 我们可以定义一个 styles 文件&#xff0c;存放全局样式。 variables.scss $fs30: 30px;mixin border() {border: 1px solid red; }main.scss use ./variables …...

关于Docker的详细介绍

Docker是一个开源的应用容器引擎&#xff0c;它允许开发者将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;然后发布到任何流行的Linux或Windows操作系统的机器上&#xff0c;实现虚拟化。以下是关于Docker的详细介绍&#xff1a; ### 一、Docker的定义 Docker是一…...

一台佳能G3811彩色喷墨打印机打印没颜色报5200的维修记录

一台佳能G3811彩色喷墨打印机,用户送修,称打印没有颜色,加电开机连电脑安驱动打印测试,确实没有颜色,于是清洗喷头结果打印机那个显示屏上 ,上来就报错P08,电脑提示5200; 话不多说,开始维修,仅记录当时的维修方法及步骤,其它未列出。。。 维修方法: 1、进维…...

【LeetCode】452.用最少数量的箭引发气球

能够找到问题的解法与把问题足够简化是天壤之别。比如我知道这题可以用贪心算法来解决&#xff0c;但是代码实现的过程中就走上了复杂的路&#xff0c;但是官方题解给的代码则相当简洁。这说明我思考的不够深入&#xff0c;导致化繁为简的能力不够强。 1. 题目 2. 分析 一道贪…...

网络安全流程规范文件解读(安全专业L1级)

技术能力项编号AQ-AQ-L1-02.01(安全维护作业计划执行能力) 课程目标&#xff1a; 通过学习课程&#xff0c;学员可以&#xff1a; 1.了解ZGYD网络安全流程规范 2.独立执行安全维护作业计划。 流程规范 一、《ZGYD远程接入安全管理办法》 二、《ZGYD网络运维账号口令管理办…...

Java、python、php版的邮件发送与过滤系统的设计与实现 (源码、调试、LW、开题、PPT)

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人 八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题可以一起交流&…...

st算法求RMP

st算法&#xff08;sparse_tabel&#xff09;可以在O(N log N)的预处理后实现O(1)的查询效率。 rmq&#xff1a;Range Maximum (Minimum) Query的缩写&#xff0c;顾名思义是用来求某个区间内的最大值或最小值&#xff0c;通常用在需要多次询问一些区间的最值得问题中。 #inc…...

零基础学习Redis(1) -- Redis简介

Redis是一个在内存中存储数据的一个中间组件&#xff0c;可用作数据库或数据缓存&#xff0c;通常在分布式系统中使用 &#xff08;不了解分布式&#xff1f; 点击传送&#xff09; 1. Redis特性 在内存中存储数据&#xff0c;通过键值对的方法存储key为string&#xff0c;v…...

安装MySQL数据库【后端 8】

安装MySQL数据库 MySQL是世界上最流行的开源关系型数据库管理系统&#xff08;RDBMS&#xff09;之一&#xff0c;广泛应用于Web应用程序开发中。无论你是初学者还是有一定经验的开发者&#xff0c;掌握MySQL的安装都是必不可少的技能。本文将指导你如何在不同的操作系统上安装…...

JAVA学习-练习试用Java实现“整数转换英文表示”

问题&#xff1a; 将非负整数 num 转换为其对应的英文表示。示例 1&#xff1a; 输入&#xff1a;num 123 输出&#xff1a;"One Hundred Twenty Three" 示例 2&#xff1a; 输入&#xff1a;num 12345 输出&#xff1a;"Twelve Thousand Three Hundred Fo…...

TPshop商城的保姆教程(Ubuntu)

1.上传TPSHOP源码 选择适合自己的版本下载 TPshop商城源文件下载链接&#xff1a; 百度网盘 请输入提取码 上传tpshop的源码包到特定目录/var/www/html 切换到/var/www/html 目录下 cd /var/www/html修改HTML目录下所有文件权限 chmod -R 777 * 2.打开网址配置 TPshop安…...

MySQL存储过程、触发器、视图

数据库中的存储过程、触发器和视图是三种常用的数据库对象&#xff0c;它们在管理数据、优化性能和简化复杂操作中起着重要的作用。下面是每种对象的详细介绍和示例&#xff1a; 存储过程 存储过程是一组为了执行特定功能而预编译的SQL语句。它类似于编程中的函数&#xff0c…...

每一行txt文件的内容将作为CSV文件中的一行,逗号、空格和句号,冒号作为分隔符拆分成多列

将指定文件夹中的每个txt文件的内容读取出来&#xff0c;并将每个文件的内容按逗号、空格和句号作为分隔符拆分成多列&#xff0c;每一行txt文件的内容将作为CSV文件中的一行&#xff0c;此文件夹中的文件会有非utf-8字符&#xff0c;是如下的代码&#xff0c;如果是utf-8编码的…...

基于inotif的文件同步备份

1 ftp 因为服务器是linux的&#xff0c;而备份服务器是windows server的&#xff0c;故而采取lftp进行同步文件。 1.1 全量同步 cat > /appdata/script/sync_all.sh <<EOF #!/bin/bash # FTP 服务器信息 FTP_SERVER"ftp://192.168.0.5" FTP_USER"…...

luckyexcel 编辑预览excel文件

luckyexcel 编辑预览excel文件 支持后端传文件流预览编辑&#xff0c;也支持选择本地文件编辑预览 看效果 上代码 <template><div style"margin: 30px"><div class"button-box2"><div><div style"color: red">…...

记录Java使用websocket

实现场景&#xff1a;每在小程序中添加一条数据时&#xff0c;后台将主动推送一个标记给PC端&#xff0c;PC端接收到标记将进行自动播放音频。 import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import or…...

(javaweb)分层解耦

目录 一.三层架构 二.分层解耦 三.IOC&DI入门 四.IOC详解 五. DI详解 一.三层架构 复用性差&#xff0c;难以维护和管理 前端发起请求&#xff0c;先会到达controller&#xff0c;再调用service进行逻辑处理&#xff0c;逻辑处理的前提是先拿到数据&#xff0c;到dao…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案

随着新能源汽车的快速普及&#xff0c;充电桩作为核心配套设施&#xff0c;其安全性与可靠性备受关注。然而&#xff0c;在高温、高负荷运行环境下&#xff0c;充电桩的散热问题与消防安全隐患日益凸显&#xff0c;成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

Mac下Android Studio扫描根目录卡死问题记录

环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中&#xff0c;提示一个依赖外部头文件的cpp源文件需要同步&#xff0c;点…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...

android RelativeLayout布局

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...