【设计模式深度剖析】【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 {

    @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类实例,返回给客户端。

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

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

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

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

相关文章

Upstream最新发布2024年汽车网络安全报告-百度网盘下载

Upstream最新发布2024年汽车网络安全报告-百度网盘下载 2024年2月7日&#xff0c;Upstream Security发布了2024年Upstream《GLOBAL AUTOMOTIVE CYBERSECURITY REPORT》。这份报告的第六版着重介绍了汽车网络安全的拐点&#xff1a;从实验性的黑客攻击发展到规模庞大的攻击&…

【文心智能体】创建一个属于自己的生活情感类智能体

文章目录 前言一、创建智能体二、体验 前言 智能体技术的快速发展&#xff0c;进一步激发了各行业开发者对其实际应用及用户需求的深入探索。 创建一个属于自己的智能体。文心一言提供了一个很好的平台。 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考…

“智”造产业新生态,2024SIA上海自动化展会乘势而来!

今年两会&#xff0c;我们频频听到一个热词“新质生产力”。当前&#xff0c;“加快形成新质生产力”已成为实体经济创新发展的具体指向。如何在加快形成新质生产力的新赛道上快马加鞭&#xff0c;成为下一个时代的领跑者&#xff0c;是制造业亟需面临的思考题。 在良好的市场环…

---随笔--Redis的学习以及在Spring Boot中的整合使用(RedisTemplate、Redisson分布式锁)

---随笔--Redis的学习以及在Spring Boot中的整合使用&#xff08;RedisTemplate、Redisson分布式锁&#xff09; 引言1. 什么是Redis2. Redis的数据结构3. Redis其它常用命令4. 小插曲之Redis面试常考5. 正篇&#xff1a;Redis在Spring Boot中的使用&#xff01;&#xff08;着…

NE555+74ls160+74LS20数电数字钟Multisim仿真设计

设计框图 数字钟电路的基本组成框架图如图一所示&#xff0c;它主要由多谐振荡器、计数器、译码器和数码显示器4个部分组成。 图一 数字钟电路的基本组成&#xff08;方框图&#xff09; 2、设计方案 数字钟是一个将“时”&#xff0c;“分”&#xff0c;“秒”显示于人的视…

初始MyBatis ,详细步骤运行第一个MyBatis程序,同时对应步骤MyBatis底层剖析

1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析 文章目录 1. 初始MyBatis &#xff0c;详细步骤运行第一个MyBatis程序&#xff0c;同时对应步骤MyBatis底层剖析每博一文案2. 前沿知识2.1 框架&#xff08;framework&#…

jdk17安装教程详细(jdk17安装超详细图文)

2021年9月14日JDK17 发布&#xff0c;其中不仅包含很多新语言功能&#xff0c;而且与旧版 JDK 相比&#xff0c;性能提升也非常明显。与之前 LTS 版本的 JDK 8 和 JDK 11 相比&#xff0c;JDK17 的性能提升尤为明显&#xff0c;本文将教你如何安装 相比于JDK1.8&#xff0c;JD…

【Crypto】看我回旋踢

文章目录 一、看我回旋踢二、知识点什么是ROT13&#xff1f;工作原理分析字符串格式 解题感悟 一、看我回旋踢 关键词回旋&#xff0c;盲猜ROT13 因为以 synt{ 开头&#xff0c;并以 } 结束&#xff0c;基本可以判断是ROT13 小小flag&#xff0c;拿下&#xff01; 二、知识点 …

Python中动态调用C#的dll动态链接库中方法

在Python中调用C#的dll库_哔哩哔哩_bilibili 环境准备&#xff1a; 安装 pythonnet pip install pythonnet在Python中调用C#动态链接库&#xff08;DLL&#xff09;&#xff0c;可以使用pythonnet库&#xff0c;它允许直接使用 .NET 的程序集。以下是一个示例&#xff0c;…

1+x(Java)中级题库易混淆理论题

<ALL表示小于最小 小于最高等同于小于ANY 使用USING子句&#xff0c;在使用连接字段时&#xff0c;都不能在前面加上表的前缀&#xff0c;因为此时这个字段已经是连接字段&#xff0c;不再属于某个单独的表。 数据库提供的自动将提供的数据类型数据转换为期望的数据类…

go select 原理

编译器会使用如下的流程处理 select 语句&#xff1a; 将所有的 case 转换成包含 channel 以及类型等信息的 runtime.scase 结构体。调用运行时函数 runtime.selectgo 从多个准备就绪的 channel 中选择一个可执行的 runtime.scase 结构体。通过 for 循环生成一组 if 语句&…

AIGC:AI整活!万物皆可建筑设计

在过去的一年里 AI设计爆火 各行业纷纷将之用于工作中 同时不少网友也在借助它整活 万物皆可设计 甲方骂我方案像屎一样 于是我就回馈他屎一样的方案 他有点惊喜&#xff0c;但是没话 不是吧&#xff0c;随便找了个充电头图片 也能生成建筑设计&#xff01;这都能行 鸟…

人工智能应用-实验7-胶囊网络分类minst手写数据集

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;代码&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;分析结果&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;实验总结&#x1f9e1;&#x1f9e1; &#x1f9…

微信小程序的自定义组件

一、创建自定义组件 &#xff08;1&#xff09;定义&#xff1a; 把页面重复的代码部分封装成为一个自定义组件&#xff0c;以便在不同的页面中重复使用&#xff0c;有助于代码的维护。 &#xff08;2&#xff09;组成&#xff1a; 自定义组件的组成&#xff1a;json文件&a…

Flutter 中如何优雅地使用弹框

日常开发中&#xff0c;Flutter 弹框&#xff08;Dialog&#xff09;是我们使用频率非常高的控件。无论是提示用户信息、确认用户操作&#xff0c;还是表单填写&#xff0c;弹框都能派上用场。然而&#xff0c;看似简单的弹框&#xff0c;实际使用起来却有不少坑和使用的技巧。…

el-select可选择可搜索可输入新内容

需求&#xff1a;el-form-item添加el-select&#xff0c;并且el-select可选择可搜索可输入新内容&#xff0c;并且和其他的el-input做联动&#xff0c;如果是选择&#xff0c;那么el-input自动回填数据并且不可编辑&#xff0c;如果el-select输入新的内容&#xff0c;那么el-in…

webpack5 splitChunks分割代码

首先明确webpack 自身的打包行为 当splitChunks为false时&#xff0c;此时不启用任何打包设置 可以看到&#xff0c;静态引入全都打到一个chunk里&#xff0c;动态引入会拆分出来一个chunk,这是纯webpack无配置的打包&#xff0c; webpack会给每个模块打上标记 ,如下 { m…

【HCIP学习】RSTP和MSTP

一、RSTP&#xff08;Rapid Spanning Tree Protocol&#xff0c;快速生成树&#xff09; 1、背景&#xff1a;RSTP从STP发展而来&#xff0c;具备STP的所有功能&#xff0c;可以兼容stp运行 2、RSTP与STP不同点 &#xff08;1&#xff09;减少端口状态 STP:disabled\blockin…

抵御风险——漫谈运维核心价值和方法论

要明晰什么是运维的核心价值&#xff0c;也就是要弄明白&#xff0c;从整个软件行业运维的角色定位来看&#xff0c;运维的核心价值在哪里&#xff1f;怎样增强自己实现核心价值的能力的问题。 软件产业本质其实还是工业&#xff0c;它的产品和传统的工业产品形态虽然有巨大差…

汇舟问卷:海外问卷调查如何闭坑?

大家好&#xff0c;我是汇舟问卷。有很多人问我这行有什么骗局吗&#xff1f;怎么说呢&#xff1f;其实每个行业都是真实存在的&#xff0c;也都有赚到钱的和赚不到钱的&#xff0c;那区别在哪里呢&#xff1f; 在你的源头&#xff0c;也就是教你或者说是代理加盟的上家&#…