【设计模式】-创建型模式-第2章第4讲-【原型模式】

news/2024/5/2 21:16:54/文章来源:https://blog.csdn.net/weixin_36754290/article/details/127147596

目录

1、原型模式(Prototype Pattern)概念

2、浅拷贝与深拷贝

2.1、概念

2.2、Java 中的深浅拷贝

浅拷贝:

深拷贝:

实例

浅拷贝

深拷贝的两种实现方式

方式一

方式二

3、原型模式的优缺点

4、 结尾


1、原型模式(Prototype Pattern)概念

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。

原型模式看似复杂,实际上它只是一种克隆对象的方法。现在实例化对象操作并不特别耗费性能,那为啥还需要对象克隆呢?在以下几种情况下,确实需要克隆那些已经实例化的对象:

  • 依赖于外部资源或硬件密集型操作进行新对象的创建的情况。
  • 获取相同对象在相同状态的拷贝而无需进行重复获取状态操作操作的情况。
  • 在不确定所需具体类时需要对象的实例的情况。

先来看一个原型模式的类图,如图1-1 

 图1-1

从上面类图中,我们可以看到在原型模式中,主要涉及以下两个类:

  • Prototype (抽象原型类):声明了 clone()方法的接口或基类,其中 clone() 方法必须由派生对象实现。在简单场景中,不需要这种基类,只需要直接具体类就足够了。
  • ConcretePrototype(具体原型类):用于实现或扩展 clone() 方法的类。clone() 方法必须要实现,因为它返回了类型的新实例。如果只在基类中实现了 clone() 方法,却没有在具体原型类中实现,那么当我们在具体原型类的对象上调用该方法时,会返回一个基类的抽象原型对象。

我们知道如果在接口中声明 clone() 方法,那么在编译阶段就会强制必须在类的实现里面实现 clone() 方法。如果是多继承结构中,父类实现了 clone() 方法,继承自它的子类将不会强制执行 clone() 方法。


2、浅拷贝与深拷贝

原型模式中既然提到了对象的拷贝,那么就得注意拷贝的深度。

2.1、概念

  • 浅拷贝:当拷贝的对象只包含简单数据类型(如:int 和 float)或不可变的对象(字符串)时,就直接将这些字段复制到新对象中。浅拷贝是一种仅将本对象作为拷贝内容的方法。
  • 深拷贝:当拷贝的对象包含对其他对象的引用时,如果引用对象不能共用时,就需要用到深拷贝,不仅要拷贝当前属性,还要拷贝当前属性的引用对象。 

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。 

如何实现?

 在 Java 中需要拷贝的原型类,需要实现 cloneable 接口,重写 clone() 方法,才可以实现类的拷贝。

2.2、Java 中的深浅拷贝

浅拷贝:

  1. 当类的成员变量是基本数据类型时,浅拷贝会将原对象的属性值赋值给新对象。
  2. 当类中成员变量时引用数据类型时,浅拷贝 会将 原对象的引用数据类型的地址 赋值给新对象的成员变量。也就是说 两个对象共享了同一个数据。当其中一个对象修改成员变量的值时,另外一个的值也会随之改变。

深拷贝:

  1. 无论是基本数据类型还是引用数据类型,都会去开辟额外的空间给新对象。不会出现浅拷贝中存在的问题。
  2. 当一个类中只有 基本数据类型时,浅拷贝与深拷贝是同样的。
  3. 当一个类中含有 引用数据类型是,浅拷贝只是拷贝一份引用,修改浅拷贝的值,原来的也会跟着变化。

实例

有一个叫张三的人,他想克隆自己,减轻自己的工作任务。

要实现克隆,需要实现Cloneable标志接口,并实现里面的clone方法。

浅拷贝

Person类
  • 类中为基本数据类型
  • 注意,String 不是基本数据类型,但是因为String 是final 修饰的。改变这个值 其实是new 一个新String对象,所以不会影响之前的原对象。
package com.zhaoyanfei.designpattern.prototypepattern;public class Person implements Cloneable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return super.toString()+">>{name="+this.name+",age="+this.age+"}";}@Overrideprotected Object clone() throws CloneNotSupportedException {//重写clone方法Person person = null;person = (Person) super.clone();return person;}
}

Client类 

package com.zhaoyanfei.designpattern.prototypepattern;public class Client {public static void main(String[] args) throws CloneNotSupportedException {Person person = new Person("飞哥",28);Person clonePerson = (Person) person.clone();System.out.println("源对象");System.out.println(person);System.out.println("浅拷贝对象");System.out.println(clonePerson);System.out.println("修改拷贝对象的值");clonePerson.setName("跟着飞哥学编程");clonePerson.setAge(18);System.out.println("修改拷贝对象后,源对象:"+person);System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);}}

 从上面的示例代码可以看到修改拷贝对象的值,不影响源对象的值。

 当类中含有引用数据类型的对象(女朋友)时

GirlFriend类

package com.zhaoyanfei.designpattern.prototypepattern;public class GirlFriend implements Cloneable {private String name;private float height;public GirlFriend(String name, float height) {this.name = name;this.height = height;}public String getName() {return name;}public void setName(String name) {this.name = name;}public float getHeight() {return height;}public void setHeight(float height) {this.height = height;}@Overridepublic String toString() {return super.toString()+">>{name="+this.name+",height="+this.height+"}";}@Overrideprotected Object clone() throws CloneNotSupportedException {//重写clone方法GirlFriend girlFriend = null;girlFriend = (GirlFriend) super.clone();return girlFriend;}
}

 修改 Person 类,增加 GirlFriend 引用对象,并修改 toString() 方法。

package com.zhaoyanfei.designpattern.prototypepattern;public class Person implements Cloneable {private String name;private int age;private GirlFriend girlFriend;public Person(String name, int age) {this.name = name;this.age = age;}public Person(String name, int age,GirlFriend girlFriend) {this.name = name;this.age = age;this.girlFriend = girlFriend;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public GirlFriend getGirlFriend() {return girlFriend;}public void setGirlFriend(GirlFriend girlFriend) {this.girlFriend = girlFriend;}@Overridepublic String toString() {return super.toString()+">>{name="+this.name+",age="+this.age+",girlFriend={name="+girlFriend.getName()+",height="+girlFriend.getHeight()+"}}";}@Overrideprotected Object clone() throws CloneNotSupportedException {//重写clone方法Person person = null;person = (Person) super.clone();return person;}
}

Client2 再次使用Clone方法。

package com.zhaoyanfei.designpattern.prototypepattern;public class Client2 {public static void main(String[] args) throws CloneNotSupportedException {GirlFriend girlFriend = new GirlFriend("周冬雨",160.50f);Person person = new Person("飞哥",28,girlFriend);Person clonePerson = (Person) person.clone();System.out.println("源对象");System.out.println(person);System.out.println("浅拷贝对象");System.out.println(clonePerson);System.out.println("修改拷贝对象飞哥女朋友的值");clonePerson.getGirlFriend().setName("关晓彤");clonePerson.getGirlFriend().setHeight(174);System.out.println("修改拷贝对象后,源对象:"+person);System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);}}

 查看这次代码运行结果,发现引用对象修改后,源对象的引用对象值也发生了变化。

GirlFriend 引用类型 被克隆时,只是将原有的地址 指向了被克隆的对象。

此时 GirlFriend 还只是一个对象。只是有两个引用指向同一份地址,一个地址中存放着一个值。

其中一个引用改变 了地址中的内容,另外一个也会跟着改变。

深拷贝的两种实现方式

方式一

使用 两层浅拷贝实现。

此时 我们使用深拷贝。 将 GirlFriend 对象也复制一份。

我们修改 Person 中的 clone() 方法

@Overrideprotected Object clone() throws CloneNotSupportedException {//重写clone方法Person person = null;person = (Person) super.clone();person.girlFriend = (GirlFriend) person.getGirlFriend().clone();return person;}

 此时,再次运行 Client2 ,可以看到代码运行结果如下:

 发现修改拷贝对象的值,源对象的值还是原来的值,不受拷贝对象修改的影响。

此时,进行的复制 是将整个引用类型的对象,开辟了另外的空间。

复制的对象 与原有的对象 不再共享同一个 GirlFriend.

其实这种方式 也是属于浅层次的拷贝,因为 GirlFriend 类中数据类型是基本数据类型。

如果 GirlFriend 类中 还有 一层引用数据类型的话,那还需要在 clone() 方法中再添加一层,才可以实现真正的深拷贝。

所以不太推荐使用这种方式。

方式二

使用 序列化对象 实现 深拷贝

Person,GirlFriend 需要实现 Serializable 接口

具体代码如下:

Person 类改造如下:

package com.zhaoyanfei.designpattern.prototypepattern;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;public class Person implements Cloneable ,Serializable {/*** */private static final long serialVersionUID = 8606208479723984421L;private String name;private int age;private GirlFriend girlFriend;public Person(String name, int age) {this.name = name;this.age = age;}public Person(String name, int age,GirlFriend girlFriend) {this.name = name;this.age = age;this.girlFriend = girlFriend;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public GirlFriend getGirlFriend() {return girlFriend;}public void setGirlFriend(GirlFriend girlFriend) {this.girlFriend = girlFriend;}@Overridepublic String toString() {return super.toString()+">>{name="+this.name+",age="+this.age+",girlFriend={name="+girlFriend.getName()+",height="+girlFriend.getHeight()+"}}";}@Overrideprotected Object clone() throws CloneNotSupportedException {//重写clone方法Person person = null;person = (Person) super.clone();
//		person.girlFriend = (GirlFriend) person.getGirlFriend().clone();return person;}//序列化实现深拷贝public Person deepClone(){//声明流对象ByteArrayOutputStream bos = null;ByteArrayInputStream bis = null;ObjectOutputStream oos = null;ObjectInputStream ois = null;try {//创建序列化流bos = new ByteArrayOutputStream();oos = new ObjectOutputStream(bos);//将当前对象以对象流的方式输出oos.writeObject(this);//创建反序化流bis = new ByteArrayInputStream(bos.toByteArray());ois = new ObjectInputStream(bis);//将流对象反序列化,实现类的深拷贝。return (Person) ois.readObject();} catch (Exception e) {e.printStackTrace();return null;}finally {try {//关闭资源bos.close();bis.close();oos.close();ois.close();} catch (IOException e) {e.printStackTrace();}}}
}

GirlFriend 类只需要再实现序列化 Serializable 即可

 测试类 Client3 代码如下:

package com.zhaoyanfei.designpattern.prototypepattern;public class Client3 {public static void main(String[] args) throws CloneNotSupportedException {GirlFriend girlFriend = new GirlFriend("周冬雨",160.50f);Person person = new Person("飞哥",28,girlFriend);Person clonePerson = (Person) person.deepClone();System.out.println("源对象");System.out.println(person);System.out.println("深拷贝对象");System.out.println(clonePerson);System.out.println("修改拷贝对象飞哥女朋友的值");clonePerson.getGirlFriend().setName("关晓彤");clonePerson.getGirlFriend().setHeight(174);System.out.println("修改拷贝对象后,源对象:"+person);System.out.println("修改拷贝对象后,拷贝对象:"+clonePerson);}}

 运行测试结果如下:

 这种方式同样也实现了深拷贝。

注:推荐使用第二种方式实现深拷贝。

在实践中,我们应根据具体情况来决定使用深拷贝、浅拷贝或混合拷贝。通常,浅拷贝对应于聚合关系,而深拷贝对应于组合关系。

3、原型模式的优缺点

优点:

  1. 性能提高。
  2. 逃避构造函数的约束。

缺点:

  1. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
  2. 必须实现 Cloneable 接口

4、 结语

下节分享创建型模式中的对象池模式,想要系统学习设计模式的小伙伴可以给我点个免费的关注,小编会持续完善设计模式的讲解。

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

带你一步步分析webpack是如何执行打包产物的

引入关系如图所示: 圈出来文件d是异步导入的文件。 wepback版本如图所示: 执行打包命令,产物如下图: 会生成两个js文件,一个是入口文件打包的testxx.js,还有一个是异步文件d生成的src_d_js.js。 打包后的…

CSS学习298~355(品优购+Web服务器)

1 品优购项目规划 1.1 网站制作流程 我们主要做前台页面设计 1.2 品优购项目整体介绍 项目名称: 品优购项目描述:品优购是一个电商网站,我们要完成PC端首页、列表页、注册页面的制作 1.3 品优购项目的学习目的 电商类网站比较综合,里面需要大量的布局技术,包括布局方式、…

数据结构-复杂度(深入学习版+Java版)

文章目录一、复杂度经典例子分析1、计算时间复杂度分析题1:O(NM),循环题2:O(N^2),冒泡排序题3:O(logN),二分查找题4:O(N),阶乘递归题5:O(2^N),斐波那契递归(满…

ffmpeg、ffplay、ffprobe 常用命令详解(音视频必备)

前言: 😄作者简介:小曾同学.com,小伙伴们也可以叫我小曾,一个致力于测试开发的博主⛽️ 如果文章知识点有错误的地方,还请大家指正,让我们一起学习,一起进步。😊 座右铭:…

回溯算法 - 二叉树中和为某一值的路径 字符串的排列

目录 1.二叉树中和为某一值的路径 1.1 题目描述 1.2 回溯算法的一般步骤 1.3 解题思路 1.4 代码实现 2. 字符串的排列 2.1 题目描述 2.2 解题思路 2.3 代码实现 1.二叉树中和为某一值的路径 1.1 题目描述 输入一颗二叉树的根节点root和一个整数expectNumber&#xff…

华为模拟器ensp学习笔记

CSDN话题挑战赛第2期 参赛话题:学习笔记 目录前言1️⃣如何注册eNSP设备?2️⃣如何通过SecureCRT登录eNSP模拟设备?结语前言 记录华为模拟器使用中遇到的问题 1️⃣如何注册eNSP设备? 如何注册eNSP设备 重新注册AR、WLAN设备: 启动AR时&…

模块化:CommonJS规范

目录 CommonJS规范 模块使用环境区分 核心语法 如何使用 CommonJS:服务器端使用 CommonJS:浏览器端使用 CommonJS规范 模块使用环境区分 CommonJS规范中,每一个JS文件都可以作为一个模块。模块的引入,主要区分两个环境&…

基于Java后台(Springboot框架)+前端小程序(MINA框架)+Mysql数据库的医院预约挂号小程序系统设计与实现

项目背景和意义 目的:本课题主要目标是设计并能够实现一个基于微信小程序医院预约挂号系统,前台用户使用小程序,后台管理使用基JavaMySql技术;通过后台设置医院信息、录入医院科室信息、录入医生信息、设置医生排班信息、查看预约…

(附源码)计算机毕业设计SSM毕业设计管理系统

毕设帮助,指导,本源码分享,调试部署(见文末) 3.3功能需求分析 本系统采用从上往下的步骤开发,基本功能如下: 本课题要求实现一套毕业设计管理系统,系统主要包括(管理员,教师和学生&a…

python-pyecharts基础知识

资料来源:2022新版黑马程序员python教程,8天python从入门到精通,学python看这套就够了_哔哩哔哩_bilibili 折线图 地图 动态GDP增长图 补充知识: json 1)JSON是一种轻量级的数据交互格式。可以按照JSON指定的格式去…

群晖Docker套件注册Harbor私有镜像仓库,并下载运行自己发布的Docker镜像

[群晖Docker套件注册Harbor私有镜像仓库,并下载运行自己发布的Docker镜像] 在进行微服务开发时,一些基础服务组件(Nacos、Redis、Mysql)的运行以及越来越多的业务服务组件的开发,会导致开发者电脑的内存资源紧张&#…

Android:玩转Jetpack Compose之MVI架构——基类中使用页面UiState

系列文章目录 架构一(MVP):Android:玩转RetrofitOkHttpKotlin协程 网络请求架构 架构二(MVVM):Android:玩转网络请求架构 RetrofitKotlin协程简单使用(MVVM架构模式) 架构三(MVI)&a…

吴恩达machine-learning-specialization2022第1周的optional lab

1. 使用python和numpy实现一个线性回归 要求使用梯度下降法,可视化losslossloss随着迭代次数的变化曲线 2. 说明 2.1 拟合函数 fw,b(x(i))wx(i)bf_{w,b}(x^{(i)})wx^{(i)}bfw,b​(x(i))wx(i)b 2.2 均方误差损失函数 J(w,b)12m∑i0m−1(fw,b(x(i))−y(i))2J(w,b)…

【云原生丨Kubernetes系列15】创建 ConfigMap 资源对象

前言 前⾯我们深入学习了 Servie 的使⽤, Service 是 Kubernetes 系统中⾮常重要的⼀个核⼼概念,这节课我们来学习另外⼀个⾮常重要的资源对象: ConfigMap 文章目录前言引入创建引入 应用部署的一个最佳实践是将应用所需的配置信息与程序进行…

【ML13】overfitting and underfitting 过拟合与欠拟合

过拟合与欠拟合过拟合与欠拟合概念过拟合解决办法解决办法一:在训练集中加入更多数据解决办法二:优化数据集 feature selection解决方法三:正则化 Regularization正则化线性回归Recape of Cost Function of Linear RegressionAdd the regular…

算法刷题:可交换的连续最大和

目录前言1. 题目描述2. 题目分析3. 代码实现4. 运行测试后记前言 好久没有做题了,前两天做了一道题,感觉还比较有意思,来分享一下。想学习,但是自己实在是懒,懒癌怎么治?期待着自己彻底奋发图强那一天。 …

【Ubuntu】常用软件下载与安装汇总

前言 发现很多诸如Detectron2的开源项目官方仅提供Liunx系统的安装方式,于是愤而将工作机系统换成了Ubuntu20.04,下面记录一些常用软件的安装方式,以便再次换机时能快速迁移,后续装新的软件会持续更新。 安装yum 直接安装会报错…

潜伏在ISP网络中数月的新黑客组织“Metador”

研究人员称之为“Metador”的一个以前未知的威胁因素已经入侵电信、互联网服务提供商(ISP)和大学大约两年了。 Metador的目标是中东和非洲的组织,他们的目的似乎是长期坚持间谍活动。该组织使用了两种基于Windows的恶意软件,它们被描述为“极其复杂”&a…

JavaScript:BOM

目录 一、BOM介绍 1、BOM的构成 二、window对象常用方法 1、窗口加载事件 2、window.onresize 3、confirm()方法 4、open()方法 5、setTimeout()定时器 6、this的使用 7、JS是单线程 8、JS执行机制 9、URL 10、location对象的属性 11、document对象 12、Date对象…

(附源码)计算机毕业设计ssm办公自动化系统

毕设帮助,指导,本源码分享,调试部署(见文末) 3.3系统流程和逻辑 系统业务流程图,如图所示: 图3-1登录流程图 图3-2添加信息流程图 图3-3注册信息流程图 4.1 概述 办公自动化系统基于Web服务模式,是一个适…