原型模式(七)

news2024/12/24 20:55:10

不管怎么样,都要继续充满着希望

上一章简单介绍了抽象工厂模式(六), 如果没有看过,请观看上一章

一. 原型模式

引用 菜鸟教程里面的原型模式介绍: https://www.runoob.com/design-pattern/prototype-pattern.html

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,

它提供了一种创建对象的最佳方式之一。

这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。

例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,

在需要的时候更新数据库,以此来减少数据库调用

一.一 介绍

意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

主要解决:在运行期建立和删除原型。

何时使用: 1、当一个系统应该独立于它的产品创建,构成和表示时。 2、当要实例化的类是在运行时刻指定时,例如,通过动态装载。 3、为了避免创建一个与产品类层次平行的工厂类层次时。 4、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

如何解决:利用已有的一个原型对象,快速地生成和原型对象一样的实例。

关键代码: 1、实现克隆操作,在 JAVA 实现 Cloneable 接口,重写 clone(),在 .NET 中可以使用 Object 类的 MemberwiseClone() 方法来实现对象的浅拷贝或通过序列化的方式来实现深拷贝。 2、原型模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些"易变类"拥有稳定的接口。

应用实例: 1、细胞分裂。 2、JAVA 中的 Object clone() 方法。

优点: 1、性能提高。 2、逃避构造函数的约束。

缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。

使用场景
1、资源优化场景。
2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
3、性能和安全要求的场景。
4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
5、一个对象多个修改者的场景。
6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,
可以考虑使用原型模式拷贝多个对象供调用者使用。
7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,
然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。

注意事项:与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。浅拷贝实现 Cloneable,重写,深拷贝是通过实现 Serializable 读取二进制流。

image-20230614102228392

二. 原型模式实现

定义一个对象 Sheep

@Data
public class Sheep {

    private Integer id;
    private String name;

}

二.一 未使用原型模式

测试方法:

@Test
    public void oneTest (){

        Sheep sheep = new Sheep();
        sheep.setId(1);
        sheep.setName("羊");

        Sheep newSheep = sheep;
        log.info("新的 {}", newSheep);
        newSheep.setName("名称改变了:");
        log.info("改变后的名称: {}", newSheep);
        log.info("之前老的数据: {}", sheep);
    }

image-20230614102446896

会将原先的老对象也改变。

重新构建对象, new Sheep() 的话

   @Test
    public void twoTest (){

        Sheep sheep = new Sheep();
        sheep.setId(1);
        sheep.setName("羊");

        Sheep newSheep = new Sheep();
        newSheep.setId(sheep.getId());
        newSheep.setName(sheep.getName());
        log.info("新的 {}", newSheep);
        newSheep.setName("名称改变了:");
        log.info("改变后的名称: {}", newSheep);
        log.info("之前老的数据: {}", sheep);
    }

image-20230614102610278

不会改变老的, 但每次都是新 new 对象, 需要将每一个属性都放置进去, 太复杂.

二.二 实现 Cloneable 接口,复制对象

@Data
public class ThreeSheep implements Cloneable{

    private Integer id;
    private String name;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试方法:

@Test
    public void threeTest () throws Exception{

        ThreeSheep sheep = new ThreeSheep();
        sheep.setId(1);
        sheep.setName("羊");

        ThreeSheep newSheep = (ThreeSheep) sheep.clone();
        log.info("新的 {}", newSheep);
        newSheep.setName("名称改变了:");
        log.info("改变后的名称: {}", newSheep);
        log.info("之前老的数据: {}", sheep);
    }

通过 .clone() 方法 将 原先的对象进行复制, 这就是原型模式.

image-20230614102916952

二.三 深复制,浅复制

定义一个 User 对象 和 Dept 对象, User 里面依赖 Dept 对象

二.三.一 浅复制

User

@Data
public class User implements Cloneable {
    private Integer id;
    private String name;
    private Dept dept;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Dept.java

@Data
public class Dept implements Cloneable{
    private Integer id;
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

测试方法:

    @Test
    public void fourTest() throws Exception {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        Dept dept = new Dept();
        dept.setId(1);
        dept.setName("研发部");
        user.setDept(dept);

        log.info("构建用户: {}",user);
        User cloneUser = (User) user.clone();
        cloneUser.setName("张三Copy");
        log.info("克隆后的用户: {}",cloneUser);
        log.info("之前的用户: {}",user);

        // 会发现 , Dept 同时改变了, 即 Dept 并没有 Copy 成功。
        cloneUser.getDept().setName("信息部");

        log.info("修改后的用户: {}",cloneUser);

        log.info("之前的用户: {}",user);
    }

image-20230614103313247

可以发现,会将 Dept 引用的对象 也进行修改了。

浅复制即 只复制了当前对象的普通属性, 并没有将引用属性也进行复制。

二.三.二 深复制

@Data
public class NewDept implements Cloneable{
    private Integer id;
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

注意 clone() 方法内的写法, 先进行 dept 的 clone() 再设置到 dept 属性里面。

@Data
public class NewUser implements Cloneable {
    private Integer id;
    private String name;
    private NewDept dept;

    @Override
    public Object clone() throws CloneNotSupportedException {
        NewUser newUser = (NewUser) super.clone();
        NewDept deptCopy = (NewDept) dept.clone();
        newUser.setDept(deptCopy);
        return newUser;
    }
}

测试方法:

@Test
    public void fiveTest() throws Exception {
        NewUser user = new NewUser();
        user.setId(1);
        user.setName("张三");
        NewDept dept = new NewDept();
        dept.setId(1);
        dept.setName("研发部");
        user.setDept(dept);

        log.info("构建用户: {}",user);
        NewUser cloneUser = (NewUser) user.clone();
        cloneUser.setName("张三Copy");
        log.info("克隆后的用户: {}",cloneUser);
        log.info("之前的用户: {}",user);

        cloneUser.getDept().setName("信息部");

        log.info("修改后的用户: {}",cloneUser);

        log.info("之前的用户: {}",user);
    }

image-20230614103610992

可以发现, dept 对象并没有改变, user.clone() 方法 将 user 普通属性 和 dept 对象都进行了复制。

二.四 序列化实现深复制

需要实现 Serializable 接口

@Data
public class NewDept2 implements Serializable{
    private Integer id;
    private String name;
}
@Data
public class NewUser2 implements Serializable {
    private Integer id;
    private String name;
    private NewDept2 dept;

    /**
      深复制
     */
    public Object deepClone() {
        // 创建流对象
        ByteArrayOutputStream bos = null;
        ObjectOutputStream oos = null;
        ByteArrayInputStream bis = null;
        ObjectInputStream ois = null;

        try {
            // 序列化
            bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

            // 反序列化
            bis = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bis);

            NewUser2 copyObj = (NewUser2)ois.readObject();
            return copyObj;

        }catch (Exception e) {
            e.printStackTrace();
            return null;
        }finally {
            try {
                bos.close();
                oos.close();
                bis.close();
                ois.close();
            }catch (Exception e) {

            }
        }
    }
}

测试方法:

 @Test
    public void sexTest(){
        NewUser2 user = new NewUser2();
        user.setId(1);
        user.setName("张三");
        NewDept2 dept = new NewDept2();
        dept.setId(1);
        dept.setName("研发部");
        user.setDept(dept);

        log.info("构建用户: {}",user);
        NewUser2 cloneUser = (NewUser2) user.deepClone();
        cloneUser.setName("张三Copy");
        log.info("克隆后的用户: {}",cloneUser);
        log.info("之前的用户: {}",user);

        cloneUser.getDept().setName("信息部");

        log.info("修改后的用户: {}",cloneUser);

        log.info("之前的用户: {}",user);
    }

image-20230614104034309

是实现了深复制的.

二.五 通过第三方工具类 实现深复制

如通过 hutool 的 ObjectUtil.clone 实现

NewUser2 和 NewDept2 与上面 二.四 的一样,都需要实现序列化接口

    @Test
    public void sevenTest() {
        NewUser2 user = new NewUser2();
        user.setId(1);
        user.setName("张三");
        NewDept2 dept = new NewDept2();
        dept.setId(1);
        dept.setName("研发部");
        user.setDept(dept);

        log.info("构建用户: {}",user);
        // 通过第三方对象, 需要序列化接口。
        NewUser2 cloneUser = ObjectUtil.clone(user);
        cloneUser.setName("张三Copy");
        log.info("克隆后的用户: {}",cloneUser);
        log.info("之前的用户: {}",user);
        
        cloneUser.getDept().setName("信息部");

        log.info("修改后的用户: {}",cloneUser);

        log.info("之前的用户: {}",user);
    }

深复制也是成功的。

image-20230614104219576


本章节的代码放置在 github 上:


https://github.com/yuejianli/DesignPattern/tree/develop/Prototype


谢谢您的观看,如果喜欢,请关注我,再次感谢 !!!

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

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

相关文章

为什么Potplayer是值得一用的视频播放器?

名人说:往者不可谏,来者犹可追。——语出《论语微子篇》 Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) o(‐^▽^‐)o很高兴你打开了这篇博客,跟着步骤一步步尝试安装吧。✧ 目录…

创新指南|企业创新可以3步做对深科技战略

企业在制定科技创新战略时如何采用深科技策略?那些涉及先进计算和科学技术的创新,如人工智能、区块链和量子计算等,企业需要意识到深科技的潜力和影响,通过采取三步法参与深科技创新生态,企业可以保持竞争力&#xff0…

docker 容器安全注意与https

隔离与共享: 虚拟机通过添加hypervisor层(虚拟化中间层),等虚拟出网卡,内存,cpu硬件,再在其上建立虚拟机,每个虚拟机都有自己的系统内核。docer通过隔离的方式,将文件系…

【工具】Ubuntu18非root用户安装CUDAPyTorch

文章目录 CUDA查看GPU驱动支持的最高CUDA版本CUDA download指定路径安装CUDA cuDNN验证cuda是否安装成功以及版本信息 pytorch验证pytorch是否安装成功&GPU是否可用MMDetection3D CUDA 查看GPU驱动支持的最高CUDA版本 运行命令:nvidia-smi CUDA Version: 11.…

延时函数:普通延时,硬件定时器延时,系统定时器延时

一、普通延时函数 此种延时是基于让MCU做一些无意义的循环操作来打发时间,优点是简单易懂,缺点是会占用MCU的处理资源且精度较低,主要用于程序简单、无严格时间要求的场景中。 //微秒级的延时 void delay_us(uint32_t delay_us) { volat…

C语言实战之、<<、>>

1、&(按位与) 按位与运算将两个运算分量的对应位按位遵照以下规则进行计算: 0 & 0 0, 0 & 1 0, 1 & 0 0, 1 & 1 1。 即同为 1 的位,结果为 1,否则结果为 0。 例如,设3的内部表示为…

6--Gradle进阶 - 项目的生命周期

6--Gradle进阶 - 项目的生命周期 项目的生命周期 Gradle 项目的生命周期分为三大阶段: Initialization -> Configuration -> Execution. 每个阶段都有自己的职责,具体如下图所示: Initialization 阶段主要目的是初始化构建, 它又分为两个子过程,一个是执行 Init Script,另…

转载:卷积神经网络结构组成与解释

原文链接:卷积神经网络结构组成与解释 卷积神经网络是以卷积层为主的深度网路结构,网络结构包括有卷积层、激活层、BN层、池化层、FC层、损失层等。卷积操作是对图像和滤波矩阵做内积(元素相乘再求和)的操作。 1. 卷积层 常见的…

华为OD机试真题 JavaScript 实现【货币单位换算】【2023Q1 100分】

一、题目描述 记账本上记录了若干条多国货币金额,需要转换成人民币分 (fen),汇总后输出每行记录一条金额,金额带有货币单位,格式为数字单位,可能是单独元,或者单独分,或者元与分的组合要求将这…

Vue的传值

目录 1. 属性传值 1.1 语法 1.2 属性和数据源同名 2. 反向传值 2.1 属性绑定自定义事件 简单案例: 购物车算总价案例: 2.2 v-model 组件的双向数据绑定 3. 透传(多层组件传值) 3.1 类型透传 3.2 属性穿透 v-bind"…

『2023北京智源大会』6月9日会议内容

『2023北京智源大会』6月9日上午|开幕式及全体大会 文章目录 一. 黄铁军丨智源研究院院长1. 大语言模型2. 大语言模型评测体系FlagEval3. 大语言模型生态(软硬件)4. 三大路线通向 AGI(另外2条路径) 二. Towards Machines that can Learn, Reason, and Plan(杨立昆丨图灵奖得主…

Java的垃圾回收机制详解

目录 1、C语言与Java语言垃圾回收区别 2、System.gc() 3、面试题引入Java垃圾回收 3.1 jvm怎么确定哪些对象应该进行回收 3.1.1 引用计数法 3.1.2 可达性分析算法 3.2 jvm会在什么时候进行垃圾回收的动作 3.2 jvm到底是怎么回收垃圾对象的 4、来回收算法 4.1 标记-清…

java是值传递还是引用传递

文章目录 1.前言2.java是值传递还是引用传递 1.前言 java是值传递;值传递是指在调用方法时将实际参数拷贝一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数;当传的是基本类型时,传的是值的拷贝…

探究MES系统:工业生产数字化转型的必需品

随着时代的发展,工业生产数字化转型已经成为不可避免的趋势。而MES系统作为工业生产运营管理领域中的一种重要软件系统,更是在数字化转型过程中扮演着重要的角色。 什么是MES系统 MES系统全称为制造执行系统(Manufacturing Execution Syste…

快速傅里叶变换python实现

img { margin: auto; display: block } 一、前言 我想认真写好快速傅里叶变换(Fast Fourier Transform,FFT),所以这篇文章会由浅到细,由窄到宽的讲解,但是傅里叶变换对于寻常人并不是很容易理解的,所以对于基础不牢的人…

基础知识学习---链表基础

1、本栏用来记录社招找工作过程中的内容,包括基础知识学习以及面试问题的记录等,以便于后续个人回顾学习; 暂时只有2023年3月份,第一次社招找工作的过程; 2、个人经历: 研究生期间课题是SLAM在无人机上的应…

Vue中如何进行音频与视频播放?

Vue中如何进行音频与视频播放&#xff1f; 在Vue中&#xff0c;我们可以使用HTML5的<audio>和<video>标签来实现音频和视频的播放。Vue本身并没有提供专门的音频或视频播放组件&#xff0c;但是可以使用Vue的数据绑定和生命周期钩子来控制音频和视频的播放。 音频…

【gcc, cmake, eigen, opencv,ubuntu】一.gcc介绍

文章目录 gcc介绍1.查看当前gcc 版本2.安装其他版本的gcc3.设置多个版本的优先级4.修改默认的版本5.查看cpu信息 gcc介绍 gcc介绍和makefile介绍 1.查看当前gcc 版本 gcc --version2.安装其他版本的gcc sudo apt install gcc-10 g-10这样我们电脑里包含gcc-9 和 gcc-10两个…

[玩游戏想道理]战略的感受

game&#xff1a;金铲铲之战 战略的定义可以说是非常多了&#xff0c;这里主要是就游戏中的过程谈一些实践感受和理解&#xff1b; 这里也不是谈战略的定义xxxx&#xff0c;这方面的理论包括《孙子兵法》都是强太多了&#xff0c;主要就是在游戏这个“现实模拟器”中&#xff0…