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

news/2024/11/26 4:25:55/

🧑‍💻作者:猫十二懿

🏡账号: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.ppmy.cn/news/101622.html

相关文章

单例模式总结

(153条消息) 解决线程安全问题&&单例模式_Master_hl的博客-CSDN博客 饿汉式 在类初始化时直接创建实例对象,不管你是否需要这个对象都会创建 直接实例化饿汉式(简洁直观) 特点:构造器私有化、自行创建且用静态变量保…

LiangGaRy-学习笔记-Day17

1、磁盘的介绍 自动分区、手工分区、命令工具分区 1.1、磁盘分类 根据介质来区分: 机械硬盘和固态硬盘 通过盘大小: 3.5英寸和2.5英寸 通过接口分类: SAS、SATA、FC scisi 根据功能: 桌面和企业级别 1.2、磁盘类型 HD…

Java笔记039-多用户即时通信系统

目录 多用户即时通信系统 项目介绍 项目演示 项目结构 涉及到Java的技术 项目开发流程 需求分析 界面设计 功能实现-用户登录 1、功能说明 2、思路分析程序框架图 3、代码实现 功能实现-拉去在线用户列表 1、功能说明 2、思路分析程序框架图 3、代码实现 功能…

vue 自动收集依赖

class Dep {constructor() {this.subscribers new Set()}//添加依赖depend() {if(activeEffect) {this.subscribers.add(activeEffect)}}//执行依赖notify() {this.subscribers.forEach(effect >{effect()})} } const dep new Dep() let activeEffect null function watc…

Quest 3初体验,或是苹果MR最大竞争对手

随着苹果MR临近,我们从彭博Mark Gurman了解到更多消息。昨日,Mark Gurman发布了Quest 3上手体验文章,并认为Quest 3可能是苹果MR头显最大的竞争对手。 1,Meta是XR头显领导者 尽管WWDC 23苹果MR将会成为最大的主角,但…

【自然语言处理】- 作业6: 面向新冠肺炎的社会计算应用

课程链接: 清华大学驭风计划 代码仓库:Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的,其分为四门课,包括: 机器学习(张敏教授) , 深度学习(胡晓林教授), 计算…

2023年上半年软件设计师考试上午试题(专业解析+参考答案)

软考试题: 涵盖软件设计师、网络规划设计师、系统分析师、系统架构设计师、信息系统项目管理师、系统规划管理师、系统集成项目管理师、软件测评师、数据库系统工程师、网络工程师、信息系统管理工程师、信息系统监理师、电子商务设计师、信息安全工程师、嵌入式系…

【WPF】数据绑定,资源字典

数据绑定 将数据与视图分开,创建MainViewModel .cs 作为数据源的处理 MainViewModel using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading…