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

news2024/11/24 1:03:49

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

目录

  • 原型模式(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 {

    @Override
    public 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
    @Setter
    private String recerver;
    //邮件标题
    @Getter
    @Setter
    private String subject;
    //称谓
    @Getter
    @Setter
    private String appellation;
    //邮件内容
    @Getter
    @Setter
    private String contxt;
    //邮件尾部,一般是加上“xxx版权所有”等信息
    @Getter
    @Setter
    private String tail;

    //构造函数
    public Mail(String subject, String contxt) {
        this.subject = subject;
        this.contxt = contxt;
    }

    //克隆方法
    @Override
    public 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.coloradmin.cn/o/1690128.html

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

相关文章

【cocos creator 】生成六边形地图

想要生成一个六边形组成的地图 完整代码示例 以下是完整的代码示例&#xff0c;包含了注释来解释每一步&#xff1a; cc.Class({extends: cc.Component,properties: {hexPrefab: {default: null,type: cc.Prefab},mapWidth: 10, // 网格的宽度&#xff08;六边形的数量&am…

基于Pytorch框架全连接神经网络对手势图片识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 手势识别是计算机视觉领域的一个重要研究方向&#xff0c;具有广泛的应用前景&#xff0c;如人…

react中怎么为props设置默认值

在React中&#xff0c;你可以使用ES6的类属性&#xff08;class properties&#xff09;或者函数组件中的默认参数&#xff08;default parameters&#xff09;来定义props的默认值。 1.类组件中定义默认props 对于类组件&#xff0c;你可以在组件内部使用defaultProps属性来…

基于Java的推箱子游戏设计与实现(论文 + 源码)

【免费】关于基于JAVA的推箱子游戏.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89325018 基于Java的推箱子游戏设计与实现 摘 要 社会在进步&#xff0c;人们生活质量也在日益提高。高强度的压力也接踵而来。社会中急需出现新的有效方式来缓解人们的压力。…

Java开发大厂面试第20讲:什么是分布式锁?Redi 怎样实现的分布式锁?

“锁”是我们实际工作和面试中无法避开的话题之一&#xff0c;正确使用锁可以保证高并发环境下程序的正确执行&#xff0c;也就是说只有使用锁才能保证多人同时访问时程序不会出现问题。 我们本课时的面试题是&#xff0c;什么是分布式锁&#xff1f;如何实现分布式锁&#xf…

秋招突击——算法打卡——5/24——无重复字符的最长字串

题目描述 实现代码 // 无重复字符的最长子串 int lengthOfLongestSubstring(string s) {int l 0,r 0;int res 0;unordered_map<char,int> temp;while(l < s.size()){temp[s.at(l)] l;for (r l 1; r < s.size() ; r) {if(temp.count(s.at(r))) break;else te…

Python UDP编程简单实例

TCP是建立可靠的连接&#xff0c;并且通信双方都可以以流的形式发送数据。 相对于TCP&#xff0c;UDP则是面向无连接的协议&#xff0c;不需要建立连接&#xff0c;只需要知道对方IP地址和端口号&#xff0c;就可以直接发送数据包。但是只管发送不保证到达。 虽然UDP传输数据…

详解 UML 中的关系概念

关联&#xff08;Association&#xff09; 表示两个类之间的一种语义性联系。例如: 学生与班级之间的关联关系。 有向关联&#xff08;Directed Association&#xff09; 关联关系有方向性,表示一个类能访问另一个类,但不一定反过来。例如: 教师能查看学生的成绩,但学生不能查…

三能一体运营体系助力政企支撑水平提升

生产力的发展是现代社会孜孜不倦的追求&#xff0c;由此产生了我们熟悉的“机械化、电子化、信息化”乃至现今正在发生的“智能化”四次工业革命。这些是由技术的突破性发展带来的&#xff0c;但我们也注意到生产力发展的另一个助力&#xff0c;即生产效率的提升&#xff0c;19…

PE文件(六)新增节-添加代码作业

一.手动新增节添加代码 1.当预备条件都满足&#xff0c;节表结尾没有相关数据时&#xff1a; 现在我们将ipmsg.exe用winhex打开&#xff0c;在节的最后新增一个节用于存放我们要增加的数据 注意&#xff1a;飞鸽的文件对齐和内存对齐是一致的 先判断节表末尾到第一个节之间…

系统架构师-考试-基础题-错题集锦1

系统架构师-考试-基础题-错题集锦 1.当一台服务器出现故障时将业务迁移到另外一台物理服务器上&#xff0c;保障了业务的连续性。 2.面向对象&#xff1a; 实体类&#xff0c;边界类&#xff0c;控制类 3.RUP&#xff1a;UP&#xff0c;统一过程&#xff0c;以架构为中心&am…

养鸡游戏牧场游戏已对接广告联盟功能介绍

养鸡游戏牧场游戏在对接广告联盟后&#xff0c;主要实现了以下几个功能&#xff1a; 广告展示与收益&#xff1a; 游戏内会嵌入广告联盟的广告&#xff0c;这些广告可能会以横幅、插屏、视频等多种形式出现在游戏的各个界面&#xff0c;如主界面、场景切换、任务完成等时机。广…

【深度学习02】注意力机制

1.自注意力机制 自注意力机制&#xff08;Self-Attention Mechanism&#xff09;是深度学习中的一种方法&#xff0c;广泛应用于自然语言处理和其他领域。为了更好地理解它&#xff0c;可以用一个简单的类比来解释。 类比&#xff1a;学生在课堂上做笔记 假设你是一个学生&a…

合约开发的基本结构剖析及前置知识梳理

前置知识点 上下文变量初步 合约函数的背后是transaction&#xff0c;上下文变量访问的是transaction中的信息两个上下文变量&#xff1a;tx和msg ERC20 规范代码实现Metamask测试 ganache-cli的安装 安装 npm install -g ganache-cli启动 ganache-cli如果出现以下这种…

ZooKeeper系列之ZAB协议

概述 ZooKeeper Atomic Broadcast&#xff0c;ZooKeeper原子消息广播协议。ZAB协议是为分布式协调服务ZK专门设计的一种支持崩溃恢复的原子广播协议。ZK主要依赖ZAB协议来实现分布式数据的最终一致性&#xff0c;基于该协议&#xff0c;ZK实现一种主备模式的系统架构来保持集群…

【EI会议】2024年雷达、电子与通信工程国际会议(ICREC 2024)

2024年雷达、电子与通信工程国际会议 2024 International Conference on Radar, Electronics and Communication Engineering 【1】会议简介 2024年雷达、电子与通信工程国际会议即将在深圳隆重召开。深圳&#xff0c;这座充满活力的现代化都市&#xff0c;以其卓越的科技创新…

后端之路第二站(正片)——SprintBoot之:设置请求接口

这一篇讲怎么简单结合模拟云接口&#xff0c;尝试简单的后端接接口、接受并传数据 一、下载Apifox接口文档软件 目前的企业都是采用前后端分离开发的&#xff0c;在开发阶段前后端需要统一发送请求的接口&#xff0c;前端也需要在等待后端把数据存到数据库之前&#xff0c;自己…

微信H5跳小程序 wx-open-launch-weapp ios显示且正常跳转,安卓不显示不报错解决方案

前提&#xff1a;在一切都正常(无报错&#xff0c;没有写法错误等)的情况下&#xff0c;出现这个问题: 去你的h5项目&#xff0c;用浏览器打开&#xff0c;在network随便找一个静态文件&#xff0c;在response响应标头中找找&#xff0c;是否有Content-Security-Policy这个头&…

vue2流星雨(可调角度)

新建StarBackground.vue组件 打开组件注释部分可以随机颜色 <template><div class"rain"><divv-for"(item,index) in rainNumber":key"index"class"rain-item"ref"rain-item":style"transform:rotate(…

【MySQL进阶之路 | 基础篇】触发器

1. 为什么要使用触发器 我们可能会遇到如下场景.我们有两个相互关联的表&#xff0c;如商品信息表与库存信息表.当我们向商品信息表添加一条记录时&#xff0c;为了保证数据完整性&#xff0c;也必须向库存信息表添加一条数据.我们就必须把这两个关联的操作写在程序里&#xf…