肝一肝设计模式【三】-- 原型模式

news2024/11/22 8:45:04

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门


文章目录

  • 系列文章目录
  • 前言
  • 一、什么是原型模式
  • 二、浅克隆
  • 三、深克隆
  • 写在最后


前言

前文中我们知道设计模式可以分为三大类:创建型模式、结构型模式、行为型模式。创建型模式中有5种,前两节里我们分析了单例模式、工厂方法模式、抽象工厂模式,那么还有两种模式:原型模式和建造者模式,它们也属于创建型模式,这一节中我们分析一下原型模式。


一、什么是原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
原型模式要求对象实现一个可以克隆自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样的话通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再通过new去创建。

是的,我又要举例子了,兔子和老虎同一天生日(嗯,女鹅说它们同一天生日┐(・o・)┌ ),让我给它们发祝福邮件。
在这里插入图片描述
我呢比较懒,除了署名其他内容都是一样的,这时我就可以先写好祝福语,然后克隆这个祝福语,最后根据不同的署名进行发送。

写下代码:

public class PrototypeClient {
	static class Mail implements Cloneable {
		private String name;
		private String message;
		
		public Mail() {}
        
		public String getName() {
	        return name;
	    }
	
	    public void setName(String name) {
	        this.name = name;
	    }
	    
	    public String getMessage() {
	        return message;
	    }
	
	    public void setMessage(String message) {
	        this.message = message;
	    }
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setMessage("祝你生日快乐~~");
        Mail mail2 = mail.clone();
        mail.setName("兔子");
        mail2.setName("老虎");
        System.out.println(mail.toString());
        System.out.println(mail2.toString());
    }
}

我们可以看到Mail类实现了Cloneable接口,并重写clone()方法即可完成本类的复制。

我们再看下源码,发现Cloneable只是一个空接口,JDK之所以这么干只是为了在运行时通知JVM可以在该类上使用clone()方法,如果该类没有实现 Cloneable接口,则调用clone()方法就会抛出 CloneNotSupportedException异常。

日常开发中,super.clone()方法并不能满足所有需求,如果Mail类中存在引用对象属性,那么我们克隆出来的对象的该属性就会和原型对象的该属性指向同一内存地址,这就引申出浅克隆和深克隆的问题

二、浅克隆

public class PrototypeClient {
	@Data
	static class Mail implements Cloneable {
		private String name;
		private String message;
		// 我们加一个礼物List
		private List<String> gifts;
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message + ",gifts:" + gifts;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setName("兔子");
        mail.setMessage("祝你生日快乐~~");
        List<String> gifts = new ArrayList<String>();
        gifts.add("糖果");
        gifts.add("积木");
        mail.setGifts(gifts);
        
        Mail cloneMail = mail.clone();
        cloneMail.setName("老虎");
        cloneMail.getGifts().add("吉他");
        
        System.out.println(mail.toString());
        System.out.println(cloneMail.toString());
    }
}

我们新增一个属性gifts(礼物),然后给克隆对象的gifts新增一个元素,然后我们发现原型对象也发生了变化,我们期望克隆出来的对象应该与原型对象互相独立,不应该有联系,所以这显然不符合预期。

所以通过以上代码我们可以看到Java自带的clone()方法进行的是浅克隆,所有的引用对象共用了一个内存地址,那如何解决这个问题呢,也就是如何进行深克隆呢?
在Java中,如果想完成原型对象的深克隆,通常使用序列化(Serializable)的方式,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

三、深克隆

继续修改代码,增加一个deepClone()方法

public class PrototypeClient {
	@Data
	static class Mail implements Cloneable, Serializable {
		private String name;
		private String message;
		// 我们加一个礼物List
		private List<String> gifts;
	    
	    public Mail deepClone(){
	        try {
	            ByteArrayOutputStream bos = new ByteArrayOutputStream();
	            ObjectOutputStream oos = new ObjectOutputStream(bos);
	            oos.writeObject(this);
	
	            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
	            ObjectInputStream ois = new ObjectInputStream(bis);
	
	            return (Mail)ois.readObject();
	        }catch (Exception e){
	            e.printStackTrace();
	            return null;
	        }
	    }
	    
	    @Override
        protected Mail clone() {
            Mail cloneMail = null;
            try {
                cloneMail = (Mail) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return cloneMail;
        }

        @Override
        public String toString() {
            return name + ":" + message + ",gifts:" + gifts;
        }
	}
	public static void main(String[] args) {
        Mail mail = new Mail();
        mail.setName("兔子");
        mail.setMessage("祝你生日快乐~~");
        List<String> gifts = new ArrayList<String>();
        gifts.add("糖果");
        gifts.add("积木");
        mail.setGifts(gifts);
        
        // 深克隆
        Mail deepCloneMail = mail.deepClone();
        deepCloneMail.setName("老虎");
        deepCloneMail.getGifts().add("吉他");

        System.out.println(mail.toString());
        System.out.println(deepCloneMail.toString());
        System.out.println(mail == deepCloneMail);
        System.out.println(mail.getGifts() == deepCloneMail.getGifts());
    }
}

运行结果:
在这里插入图片描述
通过运行结果,我们成功完成了深克隆。


写在最后

原型模式的使用场景:

  • 类初始化的时候需要消耗大量资源的时候;
  • 获取数据库连接繁琐的时候;
  • 一个对象,有很多个修改者的时候;

原型模式的优点:

  • 可以提升性能;

原型模式的缺点:

  • 因为必须实现Cloneable 接口,所以用起来可能不太方便。

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

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

相关文章

Baumer工业相机堡盟相机如何使用偏振功能(偏振相机优点和行业应用)(C#)

项目场景&#xff1a; Baumer工业相机堡盟相机是一种高性能、高质量的工业相机&#xff0c;可用于各种应用场景&#xff0c;如物体检测、计数和识别、运动分析和图像处理。 Baumer的万兆网相机拥有出色的图像处理性能&#xff0c;可以实时传输高分辨率图像。此外&#xff0…

tp6.1 bingher/ueditor(百度编辑器)(七牛、阿里OSS第三方云)详情图文教程(2023年第二版)

之前做过一版&#xff1a;tp6 bingher/ueditor(百度编辑器)(七牛、阿里OSS第三方云)详情图文教程_我是高手高手高高手的博客-CSDN博客 登录权限是Session&#xff0c;现在系统是Cookie加jwt的Token方式验证 一、修改验证登录权限 修改文件&#xff1a; vendor\bingher\uedito…

react native ios 添加启动页 xcode14 react-native-splash-screen

最近更新xcode&#xff0c;有些配置有些不同&#xff0c;网上查的方法都是过时的&#xff0c;导致配了一段时间卡在这里&#xff0c;最后访问官网才弄好了&#xff0c;所以以后解决问题的办法先看官网再查其他各路神仙的办法。 官网的步骤&#xff1a;https://github.com/crazy…

ChatGPT原理剖析

文章目录 ChatGPT常见误解1. 罐头回应2. 网络搜寻重组 ChatGPT真正做的事——文字接龙ChatGPT背后的关键技术——预训练&#xff08;Pre-train&#xff09;一般机器是怎样学习的&#xff1f; ChatGPT带来的研究问题1. 如何精准提出需求2. 如何更改错误3. 侦测AI生成的物件4. 不…

众人围剿,GPT-5招惹了谁

目录 千人呼吁暂停AI训练代表人物分析反对原因分析信息安全人身安全失业利益 总结 GPT-4 火爆全球&#xff0c;引发了人工智能大浪潮。过去的一个月&#xff0c;OpenAI、微软、谷歌加上百度不断释放王炸&#xff0c;所有人都相信&#xff0c;AI 的就是未来的生产力。俗话说&…

如何在本地搭建Maven环境并整合进IDEA中以及创建web工程?【2023最新版】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 目录 一. 为什么要使用Maven&#xff1f;1.1 获取jar包1.2 添加jar包1.3 使用Maven便于解决jar包冲突及依赖问题 二. 什么是Maven?三. 如何…

Elasticsearch查询文档--常见API篇(附详细代码和案例图文)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将介绍Elasticsearch在Java中的几种API的使用&#xff0c;这块内容不作为面试中的重点。 如果文章有什么需要改进的地方还请大佬不吝赐教&#x1f44f;&#x1f4…

Linux Ansible任务控制(循环判断、处理程序、失败任务)

目录 Ansible的Loop循环 简单的Loop循环 数组列表方式的Loop循环 字典方式的Loop循环 基于外部变量的Loop循环 Ansible的When判断 通过魔法变量、事实变量作为条件 通过剧本执行结果的变量来作为条件 Ansible处理程序 Ansible处理失败任务 处理失败任务ignore_error…

语义分割学习笔记(二)转置卷积

目录 1.转置卷积Transposed Convolution概念 2.转置卷积操作步骤 3.转置卷积参数 4.实战案例 推荐课程&#xff1a;转置卷积&#xff08;transposed convolution&#xff09;_哔哩哔哩_bilibili 感谢霹雳吧啦Wz&#xff0c;真乃神人也。 1.转置卷积Transposed Convolutio…

分类预测 | MATLAB实现WOA-CNN鲸鱼算法优化卷积神经网络数据分类预测

分类预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆网络数据分类预测 目录 分类预测 | MATLAB实现WOA-CNN-LSTM鲸鱼算法优化卷积长短期记忆网络数据分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现WOA-CNN多特征分类预测&#xff0c;多特…

SimpleCG库安装使用

SimpleCG库是一个简单的Windows图形库&#xff0c;对GDI及窗口和消息机制进行了简单封装&#xff0c;从而达到类似Turbo C图形库的调用方法&#xff0c;目的是为了初学C语言的同学能快速上手图形界面编程。使用对象包括所有初学C语言的同学&#xff0c;尤其是中学生&#xff0c…

PAT B1049

PAT B1049 题目 给定一个正数数列&#xff0c;我们可以从中截取任意的连续的几个数&#xff0c;称为片段。例如&#xff0c;给定数列 { 0.1, 0.2, 0.3, 0.4 }&#xff0c;我们有 (0.1) (0.1, 0.2) (0.1, 0.2, 0.3) (0.1, 0.2, 0.3, 0.4) (0.2) (0.2, 0.3) (0.2, 0.3, 0.4) (0…

【】lambda表达式

文章目录 lambda表达式lambda概念lambda表达式的格式关于捕获列表常见问题: 使用lambda表达式交换两个数lambda表达式底层原理 lambda表达式 lambda概念 lambda表达式本质是一个匿名函数(因为它没有名字),恰当使用lambda表达式可以让代码变得简洁.并且可以提高代码的可读性 例…

字节后端入门 - Go 语言原理与实践

1.1什么是Go语言 1.2Go语言入门 环境 1.3基础语法 1.3.1变量 var name"value" 自己推断变量类型&#xff1b; 也可以显式类型 var c int 1 name: type(value) 常量&#xff1a; const name "value" g : a"foo" 字符串拼接 1.3.2 if else {}花括号…

Java——多线程和锁

多线程 前言:当我们打开一个网站时&#xff0c;不同部分的加载并不是先后出现的&#xff0c;是并行出现的&#xff0c;没有出现一个地方没加载完&#xff0c;别的地方就也加载不出来这种事。这个就是多线程并行运行。 当其中一个线程发生阻塞时&#xff0c;操作系统会自动执行…

系统集成项目管理工程师 笔记(第12章:项目沟通管理和干系人管理)

文章目录 12.1.2 沟通的方式 404沟通管理计划的编制过程12.2.2 制订沟通管理计划的工具 4114、沟通方法 12.3.2 管理沟通的工具 41312.4.2 控制沟通的技术和方法 4163、会议 12.5.1 项目干系人管理所涉及的过程 420项目干系人管理的具体内容&#xff1a;&#xff08;1&#xff…

C/C++文件操作/IO流

学习任务&#xff1a; ⭐认识文件。⭐学习C语言中文件如何打开和关闭。⭐学习C语言中文件的读写方法&#xff08;包括顺序读写和随机读写&#xff09;。⭐学习C语言文件操作中如何判断文件读取结束。⭐简单了解FILE缓冲区。⭐认识流。⭐学习C的IO流&#xff0c;包括标准IO流和文…

【CMake】给一个库添加用法需求(Usage Requirements)

3. 给一个库添加用法需求&#xff08;Usage Requirements&#xff09; 1. usage requirements 目标参数的用法要求&#xff08;不知道有没有更好的翻译方式&#xff09;可以更好地控制库或可执行的链接&#xff0c;并包括行&#xff0c;同时还可以更好地控制CMAKE内部目标的传…

【哈士奇赠书活动 - 17期】-〖uni-app跨平台开发与应用从入门到实践〗

文章目录 ❤️‍&#x1f525; 赠书活动 - 《uni-app跨平台开发与应用从入门到实践》❤️‍&#x1f525; 编辑推荐❤️‍&#x1f525; 抽奖方式与截止时间❤️‍&#x1f525; 赠书活动 → 获奖名单 ❤️‍&#x1f525; 赠书活动 - 《uni-app跨平台开发与应用从入门到实践》…

日志技术-Logback

一. 日志是什么&#xff1a; 输出语句的弊端&#xff1a;它只能在控制台展示&#xff0c;它不能记录在本地文件当中。 日志可以将程序运行过程中的信息直接记录在文件里面&#xff0c;做永久存储。 性能较好&#xff0c;简单来说就是运行的速度会比较快。 二. 日志技术体系、L…