创建型设计模式03-原型模式

news2025/2/24 22:30:38

🧑‍💻作者:猫十二懿

🏡账号:CSDN 、个人博客 、Github

🎊公众号:猫十二懿

原型模式

1、原型模式介绍

原型模式是一种创建型设计模式,它允许通过复制现有对象来生成新对象,而无需编写从头开始创建新对象的代码。

1.1 具体介绍

在原型模式中,我们首先创建一个原型对象,然后通过复制该对象来创建新的实例,新的对象实例不需要知道任何创建的细节,只需要知道如何复制即可得到一个与原型一模一样的新对象。这种方法比直接创建对象要快,因为在复制过程中不需要执行复杂的初始化操作。原型模式还可以减少代码重复,因为我们可以通过复制现有的对象来避免多次编写相同的创建代码。

在实现原型模式时,我们通常使用一个原型管理器来存储原型对象。这个管理器允许我们在需要时获取原型对象的副本,而不是直接创建新对象。

原型模式在许多场景中都非常有用,例如在需要创建大量相似对象的情况下。它还可以用于避免复杂的初始化操作或构造函数,并且可以使代码更加灵活和可扩展。

1.2 原型模式角色

原型模式通常包括两个角色:原型类和具体原型类。

  1. 原型类是一个抽象的类或接口,声明了用于复制自己的方法。
  2. 具体原型类是具体的实现类,在实现父类(或接口)中定义的复制方法时,需要注意实现深拷贝和浅拷贝,以确保复制出来的对象完全符合预期。

2、具体例子

2.1 违反原型模式例子

复印简历的例子,对于我们程序员来说,简历也是一个很重要的东西。

Resume

/**
 * @author Shier
 * CreateTime 2023/4/21 22:03
 * 简历类
 */
public class Resume {
    private String name;
    private String sex;
    private String age;
    private String company;
    private String workTime;

    public Resume(String name) {
        this.name = name;
    }

    /**
     * 设置个人信息
     */
    public void setPersonalInfo(String sex, String age) {
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     */
    public void setWorkExperience(String company, String workTime) {
        this.company = company;
        this.workTime = workTime;
    }

    /**
     * 展示简历
     */
    public void showResume() {
        System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex);
        System.out.println("工作经历:" + this.company + "\t时间:" + this.workTime);
    }
}

测试类:

/**
 * @author Shier
 * CreateTime 2023/4/21 22:08
 */
public class ResumeTest1 {
    public static void main(String[] args) {
        Resume resume1 = new Resume("Shier");
        resume1.setPersonalInfo("男", "19");
        resume1.setWorkExperience("鱼皮科技", "2023-04~05");

        Resume resume2 = new Resume("Shier");
        resume2.setPersonalInfo("男", "19");
        resume2.setWorkExperience("鱼皮科技", "2023-04~05");

        Resume resume3 = new Resume("Shier");
        resume3.setPersonalInfo("男", "19");
        resume3.setWorkExperience("鱼皮科技", "2023-04~05");

        resume1.showResume();
        resume2.showResume();
        resume3.showResume();
    }
}

最终结果显示:

image-20230421221412008

这样就可以得到三分简历,但是你有没有想过,如果我要准备一百分呢?是不是就要去new 一百个Resume类,来创建新的对象。这样做虽然是可以,但是重复的代码也太多了吧,做重复的工作,而且消耗的内存也多。

2.2 使用原型模式改进

image-20230421221722718

那个原型抽象类Prototype是用不着的,因为克隆实在是太常用了,所以Java提供了Cloneable接口,其中就是唯一的一个方法clone(),这样你就只需要实现这个接口就可以完成原型模式了

改进后的Resume类的UML类图如下

image-20230421221901273

具体代码如下:

/**
 * @author Shier
 * CreateTime 2023/4/21 22:03
 * 简历类
 */
public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;
    private String company;
    private String workTime;

    public Resume(String name) {
        this.name = name;
    }

    /**
     * 设置个人信息
     */
    public void setPersonalInfo(String sex, String age) {
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     */
    public void setWorkExperience(String company, String workTime) {
        this.company = company;
        this.workTime = workTime;
    }

    /**
     * 展示简历
     */
    public void showResume() {
        System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex);
        System.out.println("工作经历:" + this.company + "\t时间:" + this.workTime);
    }

    /**
     * 实现clone方法
     */
    public Resume clone() {
        Resume object = null;
        // 使用克隆对象进行克隆内容
        try {
            object = (Resume) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("克隆异常了");
            throw new RuntimeException(e);
        }
        return object;
    }
}

测试类:

/**
 * @author Shier
 * CreateTime 2023/4/21 22:08
 */
public class ResumeTest1 {
    public static void main(String[] args) {
        Resume resume1 = new Resume("Shier");
        resume1.setPersonalInfo("男", "19");
        resume1.setWorkExperience("鱼皮科技1", "2023-04~05");
        // 使用resume1进行调用clone对象
        Resume resume2 = resume1.clone();
        resume2.setWorkExperience("鱼皮科技2", "2023-04~05");

        Resume resume3 = resume1.clone();
        resume3.setWorkExperience("鱼皮科技3", "2023-04~05");

        resume1.showResume();
        resume2.showResume();
        resume3.showResume();
    }
}

结果同上

3 浅拷贝与深拷贝

现在’简历’对象里的数据都是String型的,而String是一种拥有值类型特点的特殊引用类型,super.clone()方法是这样,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用的对象;因此,原始对象及其副本引用同一对象。什么意思呢?就是说如果你的’简历’类当中有对象引用,那么引用的对象数据是不会被克隆过来的。

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。但我们可能更需要这样的一种需求,把要复制的对象所引用的对象都复制一遍

3.1 浅拷贝例子

/**
 * @author Shier
 * CreateTime 2023/4/21 22:32
 * 工作经历类
 */
public class WorkExperience {
    private String company;
    private String workTime;

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public String getWorkTime() {
        return workTime;
    }

    public void setWorkTime(String workTime) {
        this.workTime = workTime;
    }
}
/**
 * @author Shier
 * CreateTime 2023/4/21 22:03
 * 简历类
 */
public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;

    private WorkExperience workExperience;

    public Resume(String name) {
        this.name = name;
        this.workExperience = new WorkExperience();
    }

    /**
     * 设置个人信息
     */
    public void setPersonalInfo(String sex, String age) {
        this.sex = sex;
        this.age = age;
    }

    /**
     * 设置工作经历
     */
    public void setWorkExperience(String company, String workTime) {
        this.workExperience.setCompany(company);
        this.workExperience.setWorkTime(workTime);
    }

    /**
     * 展示简历
     */
    public void showResume() {
        System.out.println("姓名:" + this.name + "\t年龄" + this.age + "\t性别" + this.sex);
        System.out.println("工作经历:" + this.workExperience.getCompany() + "\t时间:" + this.workExperience.getWorkTime());
    }

    /**
     * 实现clone方法
     */
    public Resume clone() {
        Resume object = null;
        // 使用克隆对象进行克隆内容
        try {
            object = (Resume) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("克隆异常了");
            throw new RuntimeException(e);
        }
        return object;
    }
}

测试类:

/**
 * @author Shier
 * CreateTime 2023/4/21 22:08
 */
public class ResumeTest1 {
    public static void main(String[] args) {
        Resume resume1 = new Resume("Shier");
        resume1.setPersonalInfo("男", "19");
        resume1.setWorkExperience("鱼皮科技1", "2023-04~05");

        Resume resume2 = resume1.clone();
        resume2.setWorkExperience("鱼皮科技2", "2023-04~05");

        Resume resume3 = resume1.clone();
        resume3.setWorkExperience("鱼皮科技3", "2023-04~05");

        resume1.showResume();
        resume2.showResume();
        resume3.showResume();
    }
}

结果显示:

image-20230421224010716

这个结果和我们的预期的不一样,第三个把前面两个都覆盖掉了。

由于它是浅表拷贝,所以对于值类型,没什么问题,对引用类型,就只是复制了引用,对引用的对象还是指向了原来的对象,所以就会出现我给resume1、resume2、resume3三个引用设置’工作经历’,但却同时看到三个引用都是最后一次设置,因为三个引用都指向了同一个对象。

3.2 深拷贝例子

3.2.1 深拷贝介绍

深拷贝把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

深拷贝是指创建一个新对象,并将原始对象中的所有非静态字段及其关联对象的值复制到新对象中。如果字段是基本数据类型,则拷贝它们的值;如果字段是引用类型,则递归地拷贝它们所指向的对象,直到所有引用对象都被拷贝为止。因此,原始对象和副本对象将不共享任何对象。

3.2.2 改进程序

再次修改上面的程序,UML类图如下:

image-20230421224305222

修改工作经历类:

package com.shier.sourcepattern;

/**
 * @author Shier
 * CreateTime 2023/4/21 22:32
 * 工作经历类
 */
public class WorkExperience implements Cloneable{
    private String company;
    private String workTime;

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    public String getWorkTime() {
        return workTime;
    }

    public void setWorkTime(String workTime) {
        this.workTime = workTime;
    }
    /**
     * 实现clone方法
     */
    public WorkExperience clone() {
        WorkExperience object = null;
        // 使用克隆对象进行克隆内容
        try {
            object = (WorkExperience) super.clone();
        } catch (CloneNotSupportedException e) {
            System.out.println("克隆异常了");
            throw new RuntimeException(e);
        }
        return object;
    }
}

修改简历类:

/**
 * 实现clone方法
 */
public Resume clone() {
    Resume object = null;
    // 使用克隆对象进行克隆内容
    try {
        object = (Resume) super.clone();
        // 进行深拷贝
        this.workExperience = this.workExperience.clone();
    } catch (CloneNotSupportedException e) {
        System.out.println("克隆异常了");
        throw new RuntimeException(e);
    }
    return object;
}

测试的代码不用改变

运行结果显示:

image-20230421224706101

看到这个三个都是不同的,即达到了目的。

4、总结

原型模式通过使用原型管理器来存储原型对象,并在需要时获取原型对象的副本,以避免多次创建相同的对象。

原型模式优点:

  1. 可以在不编写创建代码的情况下创建新对象。
  2. 可以减少代码重复,因为我们可以通过拷贝现有对象来避免多次编写相同的创建代码。
  3. 可以减少初始化操作或构造函数,并使代码更加灵活和可扩展。

原型模式缺点:

  1. 如果拷贝操作很复杂,可能会导致性能问题。
  2. 如果对象有循环依赖关系,则需要特殊处理。

原型模式应用场景:

  1. 创建成本较大的对象:某些对象的创建过程需要耗费大量时间和资源,例如数据库连接对象、网络连接对象等。在这种情况下,使用原型模式可以避免重复创建相同的对象,从而提高系统的性能和效率。
  2. 大量相似对象的创建:某些对象可能存在大量相似的情况,例如在图形界面中创建图形对象时,往往会存在大量相似的图形对象,只是具体属性不同。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,避免从头开始创建新对象的代码。
  3. 对象的复杂组合:某些对象的创建需要组合多个对象,例如在设计图形界面中的窗口对象时,需要组合多个控件对象。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,并在需要的时候修改其中某些组件,从而简化对象的创建过程。

4.2 深浅拷贝

浅拷贝的优点:

  1. 相对于深拷贝,浅拷贝的效率更高,因为它只是拷贝引用,而不是递归地拷贝所有相关对象。
  2. 浅拷贝对于某些对象来说是合适的,例如拷贝一些不包含引用类型字段的简单对象。

浅拷贝的缺点:

  1. 如果原始对象中的引用类型字段被修改了,那么拷贝后的对象也会被修改,这可能会导致意外的副作用。

深拷贝的优点:

  1. 深拷贝可以创建完全独立的对象,与原始对象不共享任何引用对象。
  2. 深拷贝可以避免意外的副作用。

深拷贝的缺点:

  1. 相对于浅拷贝,深拷贝的效率更低,因为它需要递归地拷贝所有相关对象。
  2. 深拷贝可能会导致循环引用问题,需要特殊处理。

建新对象的代码。
3. 对象的复杂组合:某些对象的创建需要组合多个对象,例如在设计图形界面中的窗口对象时,需要组合多个控件对象。在这种情况下,使用原型模式可以通过复制现有对象来创建新对象,并在需要的时候修改其中某些组件,从而简化对象的创建过程。

4.2 深浅拷贝

浅拷贝的优点:

  1. 相对于深拷贝,浅拷贝的效率更高,因为它只是拷贝引用,而不是递归地拷贝所有相关对象。
  2. 浅拷贝对于某些对象来说是合适的,例如拷贝一些不包含引用类型字段的简单对象。

浅拷贝的缺点:

  1. 如果原始对象中的引用类型字段被修改了,那么拷贝后的对象也会被修改,这可能会导致意外的副作用。

深拷贝的优点:

  1. 深拷贝可以创建完全独立的对象,与原始对象不共享任何引用对象。
  2. 深拷贝可以避免意外的副作用。

深拷贝的缺点:

  1. 相对于浅拷贝,深拷贝的效率更低,因为它需要递归地拷贝所有相关对象。
  2. 深拷贝可能会导致循环引用问题,需要特殊处理。

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

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

相关文章

【大数据处理与可视化】五、数据聚合与分组运算

【大数据处理与可视化】五、数据聚合与分组运算 实验目的实验内容实验步骤一、案例——运动员信息的分组与聚合1、统计男篮、女篮运动员的平均年龄、身高、体重2、统计男篮运动员的平均年龄、身高、体重的极差值3、统计男篮运动员的体质指数 实验小结 实验目的 能够熟练运用gr…

NISEDIT如何发布,Qt如何发布文章?难道还有人不会(超详细教学,跟着走,不会你怪我)

一、自动发布 直接运行即可,不过多阐述。 二、手动发布 文件清单: ExamSys.exe account.txt、exam.txt Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll libstdc-6.dll、libwinpthread-1.dll、libgcc_s_sjlj-1.dll、libgcc_s_dw2-1.dll 注意&#xff1a…

Vue项目中vuex的安装和使用

Vue项目中vuex的安装和使用 1. 安装vuex2. 导入vuex包并创建store仓库3. 在main.js中导入store实例4. 检测vuex是否能正常使用 1. 安装vuex npm install vuex --save2. 导入vuex包并创建store仓库 在项目文件目录中,在src目录下创建一个名为store的文件夹&#xf…

图表控件LightningChart JS使用指南 - 如何创建仪表图

LightningChart JS是性能最高的JavaScript图表库,专注于实时数据可视化。是Web上性能最高的图表库具有出色的执行性能 - 使用高数据速率同时监控数十个数据源。 GPU加速和WebGL渲染确保您的设备的图形处理器得到有效利用,从而实现高刷新率和流畅的动画。…

【大数据处理与可视化】六、数据可视化

【大数据处理与可视化】六、数据可视化 实验目的实验内容实验步骤一、案例——画图分析某年旅游景点数据1、河北省总面积和游客量位居前三的景点2、河北省旅游量的占比哪个最多,哪个最少。 实验小结 实验目的 1.能够详述常见图表的类型和特点。 2.能够熟练运用Matp…

ELFK日志分析系统并使用Filter对日志数据进行处理

目录 一、 FilebeatELK 部署Filebeat 节点上操作 二、Filtergrok 正则捕获插件内置正则表达式调用自定义表达式调用 mutate 数据修改插件multiline 多行合并插件date 时间处理插件 一、 FilebeatELK 部署 Node1节点(2C/4G):node1/192.168.15…

【数据结构】虽然很难很抽象,但是你还是得努力弄懂的数据结构——数组,你常用但是你懂它吗

数组(Array) 数组是实现顺序存储结构的基础,数组(Array)存储具有相同数据类型的元素集合.一维数组占用一块内存空间,数组的存储单元个数称为数组容量,也称为数组长度. 每个存储单元的地址是连续的,即每个元素连续存储,计算第i个元素地址所需时间是一个常量,时间复杂度是O(1),…

【论文解读|GL-Cache 】基于组级学习的缓存替换算法

论文原文: GL-Cache: Group-level learning for efficient and high-performance caching | FAST 23 源码 地址: GitHub - Thesys-lab/fast23-GLCache: Repository for FAST23 paper GL-Cache: Group-level Learning for Efficient and High-Performance…

基于Python+百度语音的智能语音ChatGPT聊天机器人(机器学习+深度学习+语义识别)含全部工程源码 适合个人二次开发

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境Pycharm 环境ChatterBot 环境 模块实现1. 模型构建2. 服务器端3. 客户端4. 语音录入5. 接口调用6.模型训练及保存 系统测试1. 模型效果2. 模型应用 参考资料其它资料下载 前言 本项目基于机器学习和语义识别技术…

Qt翻金币小游戏详细教程(内涵所有源码、图片资源)

一、项目简介 翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始界面如下: 点击start按钮,进入下层界面,选择关卡: 在这里我们设立了20个关卡供玩家选择&…

IDEA使用技巧

1. 安装教程 1.1 安装过程 1.2 安装后的软件目录结构 目录结构: bin:容器,执行文件和启动参数等 这里以我的电脑系统(64 位 windows7, 16G 内存)为例,说明一下如何调整 VM 配置文件: 1、大家根据电脑系统…

【(Ubuntu22.04 Jammy)安装ROS 2 Iron Irwini】

ROS2 IronIrwini的Debian软件包目前可用于Ubuntu22.04 Jammy 1、安装ROS2 IronIrwini前准备 需要先安装好Ubuntu22.04 Jammy的前提下开始安装ROS2 1.1 Set locale 请确保Set locale支持UTF-8 locale # check for UTF-8sudo apt update && sudo apt install locale…

Rhapsody新手提示(1)如何在安装之后更换界面语言

DDD领域驱动设计批评文集>> 《软件方法》强化自测题集>> 《软件方法》各章合集>> 安装之后,如果还想更换界面语言,Windows平台操作如下: (1)在C:\ProgramData\IBM\Rhapsody\9.0.1x64文件夹下找到…

2023 年最新、最全、最实用的 Java 岗面试真题,已收录 GitHub

Java 面试 Java 作为编程语言中的 NO.1,选择入行做 IT 做编程开发的人,基本都把它作为首选语言,进大厂拿高薪也是大多数小伙伴们的梦想。以前 Java 岗位人才的空缺,而需求量又大,所以这种人才供不应求的现状,就是 Java 工程师的薪…

WIN32 API —— 最简单的Windows窗口封装类[通俗易懂]

1 开发语言抉择 1.1 关于开发Win32 程序的语言选择 C还是C 在决定抛弃MFC,而使用纯Win32 API 开发Window桌面程序之后,还存在一个语言的选择,这就是是否使用C。C作为C的超集,能实现所有C能实现的功能。其实反之亦然,…

机器学习 | matplotlib超详细教程

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和…

Git的安装和环境变量的配置

目录 前言一、下载Git二、安装Git三、检查是否安装成功四、 配置用户名和邮箱五、环境变量配置1. 获取git的安装路径2. 设置环境变量 前言 当我们第一次在新电脑上使用git命令的时候,会报错【git 不是内部或外部命令,也不是可运行的程序 或批处理文件】…

Typora+PicGo+阿里云OSS搭建博客图床

🏠个人主页:shark-Gao 🧑个人简介:大家好,我是shark-Gao,一个想要与大家共同进步的男人😉😉 🎉目前状况:23届毕业生,目前在某公司实习&#x1f…

中国民营快递:战事永不休

【潮汐商业评论/原创】 在中国,没有一个任何行业的“战争”在时间和烈度上可以与快递业相比。这是一场持续长达20年,融合了规模战、价格战、资本战等你能想象到的所有形态的立体化“战争”。 更令人难以置信的是,眼下这场以人力、技术为武器…

你还不会写系统?超详细驾校科考系统教程,手把手教学(内涵源码,Qt实现界面,有接口可供二次开发刷题使用)

目录 一、创建项目 二、登录界面 三、验证邮箱地址 四、验证账号密码 五、考试时间 六、初始化题库 七、布局按钮 八、提交试题 九、窗口交互 一、创建项目 新建Qt桌面应用程序,项目名:ExamSys。 类信息:类名LoginDialog继承自QDi…