设计模式之原型模式--超越实例化的魔法,从复制到创造的无限可能

2023-09-19 08:14:40

概述

什么是原型模式

    原型模式是一种创建型设计模式,它允许通过复制现有对象来创建新对象,而无需依赖显式的实例化过程。使用原型模式,我们可以在运行时动态地创建对象,并且能够轻松地生成对象的副本。

浅拷贝和深拷贝

    在原型模式中,复制对象的方式通常可以分为浅拷贝和深拷贝两种。

  • 浅拷贝:浅拷贝创建一个新对象,该对象与原始对象共享一些属性。这意味着新对象中的引用类型属性仍然引用原始对象中相同的对象,对引用类型属性的更改会影响到原始对象和所有浅拷贝的对象。
  • 深拷贝:深拷贝创建一个新对象,该对象与原始对象完全独立。这意味着新对象中的引用类型属性指向新的对象,对引用类型属性的更改不会影响原始对象或其他深拷贝的对象。
    在选择浅拷贝还是深拷贝时,需要根据具体的需求和对象结构来决定。

类图

在这里插入图片描述

原型中的主要角色

  • 原型(Prototype):原型是一个抽象类或接口,定义了可以复制自己的方法 clone()。具体的原型对象需要实现这个方法来进行对象的复制。

  • 具体原型(Concrete Prototype):具体原型是实现了原型接口的具体类。它通过实现 clone() 方法来复制自身,并生成一个新的对象,保持与原始对象相似的状态。

  • 客户端(Client):客户端是使用原型对象的地方。它通过请求原型对象来创建新的对象实例,而无需直接调用构造函数进行实例化。

工作流程

    在原型模式中,客户端通过以下步骤使用原型对象:

  1. 创建一个原型对象的实例。 通过调用原型对象的 clone() 方法复制自己,从而得到一个新的对象。

  2. 对新对象进行进一步的定制和修改,使其符合需求。
    在这里插入图片描述

代码衍化过程

业务需求:打印简历

初版

业务:打印个人简历,包括个人信息,工作经历

//简历类
public class Resume {
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;

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

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

    }
    //展示简历
    public void display(){
        System.out.println(this.name+" " +this.sex+" "+this.age);
        System.out.println("工作经历"+this.timeArea+" "+this.company);
    }

//客户端
public class Client {
    public static void main(String[] args) {
        Resume resume1=new Resume("张三");
        resume1.setPersonalInfo("男","22");
        resume1.setWorkExperience("2020-2023","XX公司");
        resume1.display();

        Resume resume2=new Resume("张三");
        resume2.setPersonalInfo("男","22");
        resume2.setWorkExperience("2020-2023","XX公司");
        resume2.display();

        Resume resume3=new Resume("张三");
        resume3.setPersonalInfo("男","22");
        resume3.setWorkExperience("2020-2023","XX公司");
        resume3.display();
    }
}

}

    一个简历类,在客户端中通过实例化简历类传入相同的信息来实现打印多份简历
    问题:客户端麻烦,打印几份简历客户端就是要实例化几次,而且调整简历内容,每个都要调整。
    引入原型模式

原型模式基本代码

//原型
abstract class Prototype implements Cloneable{
    private String id;
    public Prototype(String id){
        this.id=id;

    }
    public String getId() {
        return id;
    }

   //原型的关键方法
    public Object clone(){
        Object object=null;
        try {
            object=super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }
}

//具体原型
public class ConcreteProtype extends Prototype{
    public ConcreteProtype(String id){
        super(id);
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        ConcreteProtype p1=new ConcreteProtype("编号123456");
        System.out.println("原型ID:"+p1.getId());

        ConcreteProtype c1=(ConcreteProtype)p1.clone();
        System.out.println("克隆ID:"+c1.getId());
    }

}

运行结果
在这里插入图片描述
    这里不用实例化ConcreteProtype ,直接使用克隆对象,对于Java而言,Prototype 是用不上的,因为Java提供了Cloneable接口,其中唯一的方法clone(),使用时直接实现这个接口就可以完成原型模式了。

简历的原型实现

//简历类
public class Resume implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;

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

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

    }
    //展示简历
    public void display(){
        System.out.println(this.name+" " +this.sex+" "+this.age);
        System.out.println("工作经历"+this.timeArea+" "+this.company);
    }

    public Resume Clone(){
        Resume object=null;
        try {
            object=(Resume) super.clone();
        } catch (CloneNotSupportedException e) {
           System.err.println("clone异常");
        }
        return object;
    }
}

//客户端
public class Client {
    public static void main(String[] args) {
        Resume resume1=new Resume("大鸟");
        resume1.setPersonalInfo("男","23");
        resume1.setWorkExperience("2020-2023","Xx公司");

        Resume resume2=resume1.Clone();
        resume2.setWorkExperience("2022-2023","YY公司");

        Resume resume3=resume1.Clone();
        resume3.setPersonalInfo("男","24");

        resume1.display();
        resume2.display();
        resume3.display();

    }
}

    在上面的代码中,Resume 类实现了 Cloneable 接口,表明该类可以被克隆。

    Clone() 方法重写了 Object 类的 clone() 方法,通过调用 super.clone() 实现浅拷贝,并返回一个克隆后的新对象。

    客户端的代码就简单很多,要是想改某份简历,只需要对这份简历做一定的修改就可以了,不会影响到其他简历,而且提高了效率,不用重新初始化对象,而是动态地获得对象运行时的状态。一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高。

浅复制

//工作经历类
public class WorkExperience {
    //工作时间范围
    private String timeArea;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    //所在公司
    private String company;

    public String getCompany() {
        return company;
    }

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

//简历类
public class Resume implements Cloneable {
    private String name;
    private String sex;
    private String age;
    private WorkExperience work;//声明一个工作经历对象
    public Resume(String name){
        this.name=name;
        this.work=new WorkExperience();//实例化一个工作 经历对象
    }

    public void setPersonalInfo(String sex,String age){
        this.age=age;
        this.sex=sex;
    }

    //设置工作经历
    public void setWorkExperience(String timeArea,String company){
        this.work.setTimeArea(timeArea);
        this.work.setCompany(company);;

    }
    //展示简历
    public void display(){
        System.out.println(this.name+" " +this.sex+" "+this.age);
        System.out.println("工作经历"+this.work.getTimeArea()+" " +this.work.getCompany());
    }

    public Resume clone(){
        Resume object=null;
        try {
            object=(Resume) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }



}

//客户端
public class Client {
    public static void main(String[] args) {
        Resume resume1=new Resume("张三");
        resume1.setPersonalInfo("男","30");
        resume1.setWorkExperience("2000-2010","Xx公司");

        Resume resume2=resume1.clone();
        resume2.setWorkExperience("2001-2011","YY集团");

        Resume resume3=resume1.clone();
        resume3.setPersonalInfo("女","35");
        resume3.setWorkExperience("2020-2023","ZZ公司");

        resume1.display();
        resume2.display();
        resume3.display();

    }
}

运行结果
在这里插入图片描述
    为什么会出现这种情况,引用对象好像覆盖了前面的对象,这个需要了解一下clone()方法。

    Object 类的 clone() 方法是一个受保护的本地方法。代码中的super.clone() 方法内部的实现通过调用本地方法来完成对象的浅拷贝。

    实际上,Object 类的 clone() 方法的实现有一定的约束条件:

    首先,clone() 方法只能被实现了 Cloneable 接口的类调用。如果没有实现 Cloneable 接口,调用 clone() 方法将抛出 CloneNotSupportedException 异常。

    其次,clone() 方法返回的是当前对象的浅拷贝,即新对象和原始对象共享相同的数据引用

    这意味着,只有基本类型的字段会被复制,而对于非基本类型(如数组、集合、对象等),只是复制了引用而不是创建新的对象。

    由于浅拷贝只是复制了引用而不是创建新的对象,所以在使用 super.clone() 方法时需要小心处理可变对象和引用类型的字段。如果希望进行深拷贝,需要在 clone() 方法中显式地处理这些字段,以确保每个对象都是独立的副本。

深拷贝

//工作经验类也实现cloneable接口
public class WorkExperience implements Cloneable{
    private String timeArea;

    public String getTimeArea() {
        return timeArea;
    }

    public void setTimeArea(String timeArea) {
        this.timeArea = timeArea;
    }

    //所在公司
    private String company;

    public String getCompany() {
        return company;
    }

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

    public WorkExperience clone(){
       WorkExperience object=null;
        try {
            object=(WorkExperience) super.clone();
        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }
}

//简历类
public class Resume implements Cloneable{
    private String name;
    private String sex;
    private String age;
    private WorkExperience work;//声明一个工作经历对象
    public Resume(String name){
        this.name=name;
        this.work=new WorkExperience();//实例化一个工作 经历对象
    }

    public void setPersonalInfo(String sex,String age){
        this.age=age;
        this.sex=sex;
    }

    //设置工作经历
    public void setWorkExperience(String timeArea,String company){
        this.work.setTimeArea(timeArea);
        this.work.setCompany(company);;

    }
    //展示简历
    public void display(){
        System.out.println(this.name+" " +this.sex+" "+this.age);
        System.out.println("工作经历"+this.work.getTimeArea()+" " +this.work.getCompany());
    }

    public Resume clone(){
     Resume object=null;
        try {
            object=(Resume) super.clone();
            this.work= this.work.clone();//对克隆对象里的引用也进行克隆,就达到了深复制的作用

        } catch (CloneNotSupportedException e) {
            System.err.println("clone异常");
        }
        return object;
    }

//客户端不变

}

实现结果
在这里插入图片描述
这样就达到了预期的效果。

原型模式的优点

  • 动态创建对象:原型模式允许在运行时动态地创建对象,而无需显式的实例化过程。这使得我们能够根据需要创建对象的副本,而不必依赖固定的类层次结构。

  • 简化对象创建过程:通过复制现有对象,我们可以更轻松地创建新对象。这比手动设置对象的初始状态要简单得多,尤其当对象的创建过程非常复杂时。

  • 减少资源消耗:与实例化相比,原型模式的对象复制过程通常更高效。它避免了执行复杂的初始化逻辑和与构造函数相关的资源消耗。

原型模式的应用场景

  • 当系统中的对象数量很大,但创建和初始化对象的过程很复杂时,原型模式可以显著简化对象的创建过程。
  • 当需要创建对象的副本,而无需暴露对象的创建细节时,可以使用原型模式。
  • 当需要避免与对象的产生和初始化过程相关的资源消耗时,原型模式是一个很好的选择。

原型模式的创新

    原型模式在对象创建的过程中,通过复制现有对象来创建新对象,从而避免了显式的实例化过程。这种动态创建对象的方式带来了一些创新和优势:

    简化对象创建:原型模式使得创建新对象变得简单直观,通过在现有对象的基础上进行复制,避免了繁琐的初始化过程和构造函数的调用。相比传统的实例化方式,原型模式提供了更加简洁、灵活的对象创建方式。

    动态性和扩展性:原型模式允许在运行时动态地创建对象的副本。这意味着可以根据需要创建不同类型的对象,并且可以随时修改或增加现有对象的属性和行为。这种动态性和扩展性使得原型模式在设计阶段更具灵活性和适应性。

    减少资源消耗:相对于传统的实例化方式,原型模式的对象复制过程通常更高效。它避免了执行复杂的初始化逻辑和与构造函数相关的资源消耗。通过原型模式,可以复用已有对象的状态,减少了重复创建对象的开销。

    保护对象创建细节:原型模式将对象的创建细节封装在原型对象内部,对客户端隐藏了具体的创建过程。这样,客户端无需了解对象的具体创建逻辑,仅需通过原型对象进行复制即可获得新对象。这种封装性保护了对象的创建细节,提高了代码的可维护性和安全性。

总结

    原型模式允许通过复制现有对象来创建新对象,而无需显式的实例化过程。它提供了一种动态创建对象的方式,并且可以简化对象的创建过程。通过浅拷贝和深拷贝的选择,可以控制对象副本的共享程度。

更多推荐

C++ 太卷,转 Java?

最近看到知乎、牛客等论坛上关于C++很多帖子,比如:2023年大量劝入C++2023年还建议走C++方向吗?看了一圈,基本上都是说C++这个领域唯一共同点就是都使用C++语言,其它几乎没有相关性。的确是这样,比如量化交易、自动驾驶,客户端,图形学,存储数据库开发,后台开发,嵌入式等等基本上都有各自的领域知识。那么为什么

循环经济下的新赛道妃鱼助力二手奢侈品行业变革

自2016年以来,随着国家对于闲置物品利用的政策文件连续发布,我们可以明确地看到一个趋势:我国正在积极鼓励和支持循环经济的发展,这不仅是政策层面的指导,更是反映了新一代消费者的消费观念的转变,在这样的大背景趋势下,二手奢侈品市场开始崭露头角,国泰君安研究报告显示,中国闲置高端消费品零售市场规模已从2016年162亿元增

【光伏系统】将电流从直流转换为交流电的太阳能逆变器、太阳能跟踪系统来提高系统的整体性能及集成电池解决方案(Simulink仿真)

💥💥💞💞欢迎来到本博客❤️❤️💥💥🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。⛳️座右铭:行百里者,半于九十。📋📋📋本文目录如下:🎁🎁🎁目录💥1概述📚2运行结果🎉3参考文献🌈4Simulink仿真实现💥1概述本文介绍了太阳能仿真技术及其各个组成部分。太阳

NoSQL之 Redis配置与优化

目录1、关系数据库与非关系型数据库1.1关系型数据库1.2非关系型数据库1.3关系型数据库和非关系型数据库区别1.4非关系型数据库产生背景2、Redis2.1简介2.2Redis具有以下几个优点2.3Redis为什么这么快?3、Redis安装部署3.1Redis命令工具3.2redis-cli命令行工具3.3redis

金融领域:产业链知识图谱包括上市公司、行业和产品共3类实体,构建并形成了一个节点10w+,关系边16w的十万级别产业链图谱

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实战掌握技能,助力用户更好利用CSDN平台,自主完成项目设计升级,提升自身的硬实力。专栏订阅:项目大全提升自身的硬实力[专栏详细介绍:项目设计

SpaceX预计到2022年Starlink用户将达到2000万,但最终达到了100万

SpaceX的Starlink部门还没有接近实现客户和收入的预测,该公司在建立卫星网络之前与投资者分享了这一点华尔街日报报道今天出版。据报道,2015年的一份题为“SpaceX用来从投资者那里筹集资金”的报告预计,到2022年,Starlink的订户将达到2000万人,并产生近120亿美元的收入和70亿美元的营业利润。

【LLM】金融大模型场景和大模型Lora微调实战

文章目录一、金融大模型背景二、大模型的研究问题三、大模型技术路线四、LLaMA家族模型五、Lora模型微调的原理六、基于mt0-large进行Lora微调实战七、对chatglm2进行lora微调Reference一、金融大模型背景金融行业需要垂直领域LLM,因为存在金融安全和数据大多数存储在本地,在风控、精度、实时性

基于中文金融知识的 LLaMA 系微调模型的智能问答系统:LLaMA大模型训练微调推理等详细教学

项目设计集合(人工智能方向):助力新人快速实战掌握技能、自主完成项目设计升级,提升自身的硬实力(不仅限NLP、知识图谱、计算机视觉等领域):汇总有意义的项目设计集合,助力新人快速实战掌握技能,助力用户更好利用CSDN平台,自主完成项目设计升级,提升自身的硬实力。专栏订阅:项目大全提升自身的硬实力[专栏详细介绍:项目设计

malloc与free

目录前提须知:malloc:大意:头文件:申请空间:判断是否申请成功:使用空间:结果:整体代码:malloc申请的空间怎么回收呢?注意事项:free:前提须知:为什么要有动态内存分配?我们已经掌握的内存开辟⽅式有:intval=20;//在栈空间上开辟四个字节chararr[10]={0};//在栈空间上开辟10个字节

postgresql-索引与优化

postgresql-索引与优化索引简介索引类型B-树索引哈希索引GiST索引SP-GiST索引GIN索引BRIN索引创建索引唯一索引多列索引函数索引部分索引覆盖索引查看索引维护索引删除索引索引简介索引(Index)可以用于提高数据库的查询性能;但是索引也需要进行读写,同时还会占用更多的存储空间;因此了解并适当利用索引

Web 3.0 发展到什么水平了?

最初,有互联网:电线和服务器的物理基础设施,让计算机和它们前面的人相互交谈。美国政府的阿帕网在1969年发出了第一条消息,但我们今天所知道的网络直到1991年才出现,当时HTML和URL使用户可以在静态页面之间导航。将此视为只读Web或Web1。在2000年代初期,情况开始发生变化。首先,互联网的互动性越来越强;这是一

热文推荐