【设计模式深度剖析】【5】【创建型】【原型模式】| 类比群发邮件,加深理解

👈️上一篇:建造者模式    |   下一篇:创建型设计模式对比👉️

目录

  • 原型模式(Prototype Pattern)
    • 概览
    • 定义
      • 英文原话
      • 直译
    • 3个角色
      • 类图
      • 1. 抽象原型(Prototype)角色
      • 2. 具体原型(Concrete Prototype)角色
      • 3. 客户(Client)角色
      • 代码示例
        • 1. 抽象原型
        • 2. 具体原型
        • 3. 被复制的对象的类
        • 4. 客户端
        • 5. 测试类
    • 应用
      • 优点
      • 使用场景
    • 原型模式示例解析:邮件群发
      • 类图
      • 1. 抽象原型角色:Prototype.java(接口定义克隆方法)
      • 2. 具体原型类:Mail.java
      • 3. 测试类
    • 补充知识点
      • Lombok的@Builder注解原理:建造者模式
        • 1. 先说下用法
        • 2. 原理分析

原型模式(Prototype Pattern)

>>本文源码<<

概览

  • 定义
  • 3个角色
    • 代码示例
  • 应用
    • 优点
    • 使用场景
  • 案例分析
  • 原型模式示例解析:邮件群发

定义

英文原话

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

直译

指定要使用原型实例创建的对象类型,并通过复制该原型创建新对象。

3个角色

类图

Prototype

1. 抽象原型(Prototype)角色

为具体原型角色定义方法,指定统一标准

2. 具体原型(Concrete Prototype)角色

该角色是被复制的对象,必须实现抽象原型接口

Java中内置了克隆机制,Object类具有一个clone()方法,能够实现对象的克隆,使一个类支持克隆需要以下两步。

  • 实现Cloneable接口
  • 覆盖Object的clone()方法,完成对象的克隆操作,通常只需要调用Object的clone()方法(“浅克隆”-即只复制关联对象的引用)即可

3. 客户(Client)角色

该角色提出创建对象的请求

代码示例

>>示例源码<<

1. 抽象原型

抽象原型Prototype接口继承 Cloneable 接口,以标明该接口的实现类可以被复制,并声明一个 clone()方法,该clone()方法是对Object类的clone()方法的重写

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;/** 实现本接口,表明实现类可以被复制*/
public interface Prototype extends Cloneable {// 克隆方法Prototype clone();
}
2. 具体原型

具体原型ConcretePrototype实现clone()方法。这里调用的父类Object的clone()方法

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;/** 具体原型ConcretePrototype实现clone()方法*/
public class ConcretePrototype implements Prototype {@Overridepublic Prototype clone() {try {// 此处调用的Object类的clone()方法,并向下转型为抽象原型类型。// Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用;// 而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。return (Prototype) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();return null;}}
}
3. 被复制的对象的类

提供一个被复制的类,用于测试使用

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;/** 被复制的对象的类*/
@Data
@Builder
@EqualsAndHashCode(callSuper = true)
public class User extends ConcretePrototype {private String name;private Integer age;
}

这里的@Builder是lombok的注解,加上该注解编译出的class文件中,通过内部类的方式实现了建造者模式。便于用户方便地创建对象。

(关于建造者模式看上一篇 建造者模式


@Builder的原理,参考补充知识点Lombok的@Builder注解原理:建造者模式

4. 客户端

使用原型类的客户类

public class Client {// 传参传入具体原型类示例,具体原型类实现了抽象原型的clone方法public Prototype operation(Prototype example) {// 得到example的副本Prototype p = example.clone();return p;}
}
5. 测试类

新建了一个用于被复制的user对象,

调用客户类进行复制,传入原型类型的对象(User类实现了原型类,通过继承具体原型类实现的),然后返回一个原型实例,

之后测试了复制对象与原始对象属性是同一对象,

然后对复制对象的属性值通过反射进行了获取与打印

package com.polaris.designpattern.list1.creational.pattern5.prototype.proto;import java.lang.reflect.Field;/*** @author Polaris 2024/5/17*/
public class DemoTest {public static void main(String[] args) {User user = User.builder().name("历史").age(5000).build();Client client = new Client();Prototype clone = client.operation(user);System.out.println("name属性是同一个:"+((User)clone).getName().equals(user.getName()));System.out.println("age属性(>127,非读缓存值)是同一个:"+((User)clone).getAge().equals(user.getAge()));System.out.println("-----------------");Field[] declaredFields = clone.getClass().getDeclaredFields();for (Field declaredField : declaredFields) {Object o;try {String name = declaredField.getName();declaredField.setAccessible(true);o = declaredField.get(clone);System.out.println(name + ":" + o);} catch (IllegalAccessException e) {e.printStackTrace();}}}
}/* Output:name属性是同一个:true
age属性(>127,非读缓存值)是同一个:true
-----------------
name:历史
age:5000*///~

输出中:克隆前后的对象的属性指向的是同一个对象。

也印证了:

Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用

应用

优点

原型模式的优点有以下几个方面。

  1. 性能优良:原型模式是在内存二进制流的复制,要比直接new一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
  2. 逃避构造函数的约束:这既 是优点也是缺点,直接在内存中复制,构造函数是不会执行的,因此减少了约束,需要在实际应用时进行权衡考虑。

使用场景

原型模式的使用场景如下。

  1. 资源优化场景,类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
  2. 性能和安全要求的场景,通过new产生一个对象需要非常烦琐的数据准备或访问权限,可以使用原型模式。
  3. 一个对象多个修改者的场景,一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式复制多个对象供调用者使用。
  4. [注]:在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂方法提供给调用者。
    • 注意Java中Object提供的clone()方法采用的是“浅”克隆,即只复制关联对象的引用,而不复制关联对象的数据。如果需要“深”克隆,则需要在覆盖clone()方法时手动控制克隆的深度。

原型模式示例解析:邮件群发

>>示例源码<<<

本案例是一个对象多个修改者情况:

  • 每次发送邮件都是对原始邮件对象克隆然后进行属性修改
  • 原始邮件对象实现了抽象原型接口
  • 抽象原型接口继承Cloneable接口,声明提供抽象原型类型的clone方法声明
  • 原始邮件类型实现抽象原型类型的clone()方法,用于克隆对象
  • 使用Object的克隆方法来克隆邮件类型对象,浅克隆,复制邮件类对象的引用

类图

在这里插入图片描述

1. 抽象原型角色:Prototype.java(接口定义克隆方法)

package com.polaris.designpattern.list1.creational.pattern5.prototype;public interface Prototype extends Cloneable {//克隆方法Prototype clone();
}

2. 具体原型类:Mail.java

创建一个邮件类Mail:

Mail类实现Cloneable接口,并实现了clone()方法,

该方法是实现原型模式的关键,只有实现clone()方法,

在应用中才能对Mail进行复制克隆

package com.polaris.designpattern.list1.creational.pattern5.prototype;import lombok.Getter;
import lombok.Setter;public class Mail implements Prototype {//收件人@Getter@Setterprivate String recerver;//邮件标题@Getter@Setterprivate String subject;//称谓@Getter@Setterprivate String appellation;//邮件内容@Getter@Setterprivate String contxt;//邮件尾部,一般是加上“xxx版权所有”等信息@Getter@Setterprivate String tail;//构造函数public Mail(String subject, String contxt) {this.subject = subject;this.contxt = contxt;}//克隆方法@Overridepublic Prototype clone() {Mail mail = null;try {mail = (Mail) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return mail;}
}

3. 测试类

通过邮件的克隆群发演示原型模式

package com.polaris.designpattern.list1.creational.pattern5.prototype;import java.util.Random;public class DemoTest {//发送账单的数量,这个值从数据库中获得的private static int MAX_COUNT = 6;public static void main(String[] args) {//模拟发送邮件int i = 0;//定义一个邮件对象Prototype prototype = new Mail("某商场五一抽奖活动","五一抽奖活动通知:" +"凡在五一期间在本商场购物满100元的客户都有货的抽奖机会!...");((Mail)prototype).setTail("解释权归某商场所有");while (i < MAX_COUNT) {//克隆邮件Mail cloneMail = (Mail) prototype.clone();//以下是每封邮件不同的地方cloneMail.setAppellation(getRandomString(5) + " 先生(女士)");cloneMail.setRecerver(getRandomString(5) + "@" + getRandomString(8) + ".com");//发送邮件sendMail(cloneMail);i++;}}//发送邮件public static void sendMail(Mail mail) {System.out.println("标题:" + mail.getSubject() +"\t收件人:" + mail.getRecerver() + "\t...发送成功!");}//获取指定长度的随机字符串public static String getRandomString(int maxLength) {String souce = "abcdefghijklmnopqrstuvwxyz" +"ABCDEFGHIJKLMNOPQRSTUVWXYZ";StringBuilder sb = new StringBuilder();Random random = new Random();for (int i = 0; i < maxLength; i++) {sb.append(souce.charAt(random.nextInt(souce.length())));}return sb.toString();}
}/* Output:
标题:某商场五一抽奖活动	收件人:TEDiM@LXFeyNdU.com	...发送成功!
标题:某商场五一抽奖活动	收件人:qyOWv@wieXPRga.com	...发送成功!
标题:某商场五一抽奖活动	收件人:wMloC@IgFOzjBh.com	...发送成功!
标题:某商场五一抽奖活动	收件人:hrDGv@HAjpARpN.com	...发送成功!
标题:某商场五一抽奖活动	收件人:lyWwt@cLdWntpC.com	...发送成功!
标题:某商场五一抽奖活动	收件人:ZVeap@HZlYxaCe.com	...发送成功!
*///~

补充知识点

Lombok的@Builder注解原理:建造者模式

1. 先说下用法

通过下面的链式调用方式,可以非常方便的得到一个需要的user对像

User user = User.builder().name("历史").age(5000).build();
2. 原理分析

以下是标记了@Builder注解后,编译出的类文件中看到增加了以下代码:

public class User extends ConcretePrototype {private String name;private Integer age;User(String name, Integer age) {this.name = name;this.age = age;}public static User.UserBuilder builder() {return new User.UserBuilder();}public static class UserBuilder {private String name;private Integer age;UserBuilder() {}public User.UserBuilder name(String name) {this.name = name;return this;}public User.UserBuilder age(Integer age) {this.age = age;return this;}public User build() {return new User(this.name, this.age);}public String toString() {return "User.UserBuilder(name=" + this.name + ", age=" + this.age + ")";}}
}

通过标记了@Builder注解的类调用类方法builder()方法,这里创建了一个内部类UserBuilder对象

接下来就是构造者模式的实现

内部类UserBuilder就是建造者角色,

User类的内部类UserBuilder的属性和User类的属性完全一样,然后提供了对这些属性配置的方法,且每个方法都会将该实例返回,这样就可以进行链式调用。

最后提供了一个build()方法,将内部类的属性赋值给User类的构造函数,来构造User类实例,返回给客户端。

这就是建造者模式的具体应用。

👈️上一篇:建造者模式    |   下一篇:创建型设计模式对比👉️

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

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

相关文章

必示科技参与智能运维国家标准预研线下编写会议并做主题分享

近日&#xff0c;《信息技术服务 智能运维 第3部分&#xff1a;算法治理》&#xff08;拟定名&#xff09;国家标准预研阶段第一次编写工作会议在杭州举行。本次会议由浙商证券承办。 此次编写有来自银行、证券、保险、通信、高校研究机构、互联网以及技术方等29家单位&#xf…

Linux基础(四):Linux系统文件类型与文件权限

各位看官&#xff0c;好久不见&#xff0c;在正式介绍Linux的基本命令之前&#xff0c;我们首先了解一下&#xff0c;关于文件的知识。 目录 一、文件类型 二、文件权限 2.1 文件访问者的分类 2.2 文件权限 2.2.1 文件的基本权限 2.2.2 文件权限值的表示方法 三、修改文…

CSS3 新增背景属性 + 新增边框属性(如果想知道CSS3新增背景属性和新增边框属性的知识点,那么只看这一篇就够了!)

前言&#xff1a;CSS3在CSS2的基础上&#xff0c;新增了很多强大的新功能&#xff0c;从而解决一些实际面临的问题&#xff0c;本篇文章主要讲解的为CSS3新增背景属性和新增边框属性。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解更多内容可以访问我的主页秋刀鱼不做梦-CSD…

视觉SLAM十四讲:从理论到实践(Chapter5:相机与图像)

前言 学习笔记&#xff0c;仅供学习&#xff0c;不做商用&#xff0c;如有侵权&#xff0c;联系我删除即可 目标 理解针孔相机的模型、内参与径向畸变参数。理解一个空间点是如何投影到相机成像平面的。掌握OpenCV的图像存储与表达方式。学会基本的摄像头标定方法。 一、相…

机器学习第四十周周报 WDN GGNN

文章目录 week40 WDN GGNN摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构3.1 问题提出3.2 GNN3.3 CSI GGNN 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.3.1 数据获取4.3.2 参数设置4.3.3 实验结果 5. 结论二、GGNN1. 代码解释2. 网络结构小结参考文献参考文…

基础3 探索JAVA图形编程桌面:逻辑图形组件实现

在一个宽敞明亮的培训教室里&#xff0c;阳光透过窗户柔和地洒在地上&#xff0c;教室里摆放着整齐的桌椅。卧龙站在讲台上&#xff0c;面带微笑&#xff0c;手里拿着激光笔&#xff0c;他的眼神中充满了热情和期待。他的声音清晰而洪亮&#xff0c;传遍了整个教室&#xff1a;…

Hsql每日一题 | day02

前言 就一直向前走吧&#xff0c;沿途的花终将绽放~ 题目&#xff1a;主播同时在线人数问题 如下为某直播平台主播开播及关播时间&#xff0c;根据该数据计算出平台最高峰同时在线的主播人数。 id stt edt 1001,2021-06-14 12:12:12,2021-06-14 18:1…

【错误解决】使用HuggingFaceInstructEmbeddings时的一个错误

起因&#xff1a;使用huggingface构建一个问答程序时出现的问题。 错误内容&#xff1a; 分析&#xff1a; 查看代码发现&#xff0c;HuggingFaceInstructEmbeddings和sentence-transformers模块版本不兼容导致。 可以明显看到方法参数不同。 解决&#xff1a; 安装sentenc…

element-ui的Form 表单有些项的参数校验

项目场景&#xff1a; 提示&#xff1a;项目相关背景&#xff1a; 项目场景&#xff1a;有时候自己的Form 表单中的某几项引入的一些项不好去校验 这样的咋去校验呢&#xff1f; 解决方案&#xff1a; 提示&#xff1a;问题的具体解决方案&#xff1a; 例如&#xff1a;写一…

【pyspark速成专家】3_Spark之RDD编程1

目录 ​编辑 一&#xff0c;创建RDD 二&#xff0c;常用Action操作 三&#xff0c;常用Transformation操作 一&#xff0c;创建RDD 创建RDD主要有两种方式&#xff0c;一个是textFile加载本地或者集群文件系统中的数据&#xff0c; 第二个是用parallelize方法将Driver中的…

fortran77 初始化矩阵 打印矩阵 模版 备拷

1&#xff0c;源码 SUBROUTINE INIT_MATRIX(A, m, n, lda)DOUBLE PRECISION A(*)CALL SRAND(2024)DO i1, mDO j1, nA(i lda*(j-1)) RAND() RAND() C WRITE(*, (F8.4)) A(i)END DOEND DOENDSUBROUTINE PRINT_MATRIX(A, m, n, lda)DOUBLE PREC…

【Vue3】封装axios请求(cli和vite)

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 Vue 【Vue3】env环境变量的配置和使用&#xff08;区分cli和vite&#xff09; 文章目录 Vue前言一、常见用法二、vue3cli封装接口1..env配置2..dev(开…

ADC协议详解

文章目录 简介工作流程原理图时序图 优点与缺点 简介 模数转换器&#xff08;ADC&#xff0c;Analog-to-Digital Converter&#xff09;是一种将模拟信号转换为数字信号的电子设备。模拟信号通常表示物理测量的连续变化&#xff0c;如声音、温度、压力等&#xff0c;而数字信号…

codewars check_same_case 题解

题目 编写一个函数来检查两个给定的字符是否大小写相同。 如果任何字符不是字母&#xff0c;则返回-1如果两个字符大小写相同&#xff0c;则返回1如果两个字符都是字母且大小写不同&#xff0c;则返回0 例子 a并g返回1A并C返回1b并G返回0B并g返回00并?返回-1题解 1 此题主…

AI大模型与产品策略:产品经理的致胜之道

随着AI大模型的快速进化&#xff0c;其生态的构建&#xff0c;已经从C端过度到了B端。 作为产品经理&#xff0c;我们应该及时响应大趋势&#xff0c;在产品策略上融入AI大模型模块&#xff0c;深度挖掘AI大模型的应用价值&#xff0c;这才是作为PM在现阶段最有价值的地方。 …

想学接口测试,不知道那个工具适合?

引言&#xff1a; 接口测试在软件开发中扮演着至关重要的角色&#xff0c;它可以帮助我们验证系统的功能、性能和安全性。而选择适合的工具是进行接口测试的重要一步。本文将从零开始&#xff0c;为你详细介绍如何选择合适的工具&#xff0c;并提供规范的指导。 一、了解接口…

初识C语言——第二十八天

代码练习1&#xff1a; 用函数的方式实现9*9乘法表 void print_table(int n) {int i 0;int j 0;for (i 1; i< n; i){for (j 1; j< i; j){printf("%d*%d%-3d ", i, j, i * j);}printf("\n");}}int main() {int n 0;scanf("%d", &a…

汉明码(海明码)的计算的规则

一.汉明码的由来 1.汉明码&#xff08;Hamming Code&#xff09;&#xff0c;是在电信领域的一种线性调试码&#xff0c;以发明者理查德卫斯里汉明的名字命名。汉明码在传输的消息流中插入验证码&#xff0c;当计算机存储或移动数据时&#xff0c;可能会产生数据位错误&#x…

【VUE】 如何关闭ESlint的自动修复功能

问题描述例如&#xff1a;原书写代码ESLint自动修复报错如下 方案一、在文件中添加屏蔽警告的代码html代码中JavaScript代码中 方案二、关闭ESLint的自动修复功能1、VSCode 扩展找到 ESLint 插件2、在设置中找到在 settings,json 中编辑3、将"autoFix": true改为&quo…

4.双指针+递归

一、双指针编程技巧 方法参数传递数组 将数组通过方法参数传递&#xff0c;方法操作的数组和main方法中的数组指向同一块内存区域&#xff0c;意味着方法操作数组&#xff0c;同时会引起main方法中数组的改变以引用的方式作为方法参数进行传递的 元素交换 定义临时变量temp&a…