二、原型模式

news2024/9/21 22:28:41

文章目录

  • 1 基本介绍
  • 2 实现方式
    • 深浅拷贝
    • 目标
    • 2.1 使用 Object 的 clone() 方法
      • 2.1.1 代码
      • 2.1.2 特性
      • 2.1.3 实现深拷贝
    • 2.2 在 clone() 方法中使用序列化
      • 2.2.1 代码
    • 2.2.2 特性
  • 3 实现的要点
  • 4 Spring 中的原型模式
  • 5 原型模式的类图及角色
    • 5.1 类图
      • 5.1.1 不限制语言
      • 5.1.2 在 Java 中的类图
    • 5.2 角色
      • 5.2.1 Prototype ( 原型 )
      • 5.2.2 ConcretePrototype ( 具体的原型 )
      • 5.2.3 Client ( 使用者 )
  • 6 原型模式的优缺点
  • 7 原型模式的使用场景
  • 8 总结

1 基本介绍

原型模式(Prototype Pattern)是一种创建型设计模式,它允许 通过复制已有的对象来创建新的对象,而无需知道对象创建的细节

2 实现方式

深浅拷贝

原型模式的实现围绕着 深浅拷贝 的特性展开,其定义如下:

  • 浅拷贝(Shallow Copy):只复制对象的 第一层 属性(即 八大基本数据类型 + String 这个引用数据类型),对于引用类型的属性(除了字符串 String 类型外),复制的是内存地址引用,而非对象本身。它存在这种危险:如果原对象的引用类型属性被修改,浅拷贝得到的对象的对应属性也会受到影响
  • 深拷贝(Deep Copy):递归地 复制对象及其所有子对象,创建一个全新的对象,与原对象没有任何关联。

目标

实现一个 Sheep 类,字段为 String nameint age,它能通过 clone() 方法克隆出一模一样的对象。

共有以下 2 种实现:

2.1 使用 Object 的 clone() 方法

2.1.1 代码

public class Sheep implements Cloneable { // 实现了 Cloneable 接口
    private String name;
    private int age;
    private String friend;

    public Sheep(String name, int age, String friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getFriend() {
        return friend;
    }

    @Override
    public Sheep clone() {
        try {
            return (Sheep) super.clone(); // 使用 Object 的 clone()
        } catch (Exception e) { // 如果有异常
            e.printStackTrace(); // 打印异常
            return null; // 并返回 null
        }
    }
    
    public static void main(String[] args) { // 测试程序
        Sheep sheep = new Sheep("silvery", 6, new Sheep("gold", 13, null));
        Sheep clone = sheep.clone();
        System.out.println(sheep == clone);
        System.out.println("[sheep]: name = " + sheep.getName() + ", age = " + sheep.getAge()
                + ", friend = " + sheep.getFriend() + ", friend.name = " + sheep.getFriend().getName());
        System.out.println("[clone]: name = " + clone.getName() + ", age = " + clone.getAge()
                + ", friend = " + clone.getFriend() + ", friend.name = " + clone.getFriend().getName());
        /* 测试结果如下:
            false
            [sheep]: name = silvery, age = 6, friend = cn.me.Sheep@f6f4d33, friend.name = gold
            [clone]: name = silvery, age = 6, friend = cn.me.Sheep@f6f4d33, friend.name = gold
            总结:
            sheep 和 name 不是同一个对象,但属性一样
            它们的 friend 是同一个 friend,这就是 浅拷贝
         */
    }
}

2.1.2 特性

  • 浅拷贝Object 自带的 clone() 方法是浅拷贝,对于非 String 类型的引用类型,它只能拷贝其内存地址。
  • 实现简单:虽然这种方式是浅拷贝,但实现起来很简单,如果能确定不使用除 String 类型之外的引用类型,这种方式很方便。

2.1.3 实现深拷贝

如果一个类中除 String 类型之外的引用类型 很少,那么可以像以下这样 单独 对这些引用类型进行 克隆,从而实现深拷贝:

@Override
public Sheep clone() {
    try {
        Sheep clone = (Sheep) super.clone();
        
        // 对 除 String 之外的引用类型 单独克隆
        // 如果没有判断空值,则可能会抛出空指针异常 NullPointerException
        if (clone.friend != null) {
            clone.friend = this.friend.clone();
        }
        
        return clone;
    } catch (Exception e) { // 如果有异常
        e.printStackTrace(); // 打印异常
        return null; // 并返回 null
    }
}

2.2 在 clone() 方法中使用序列化

2.2.1 代码

注意:如果要使用序列化,则要实现 Serializable 接口

public class Sheep implements Cloneable, Serializable { // 实现了 Cloneable 和 Serializable 接口
    private String name;
    private int age;
    private String friend;

    public Sheep(String name, int age, String friend) {
        this.name = name;
        this.age = age;
        this.friend = friend;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public String getFriend() {
        return friend;
    }
    
    @Override
    public Sheep clone() {
        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);
            Object clone = ois.readObject();

            return (Sheep) clone;
        } catch (Exception e) { // 如果有异常
            e.printStackTrace(); // 打印异常
            return null; // 并返回 null
        } finally {
            try { // 用完流之后记得关闭
                if (bos != null) {
                    bos.close();
                }
                if (oos != null) {
                    oos.close();
                }
                if (bis != null) {
                    bis.close();
                }
                if (ois != null) {
                    ois.close();
                }
            } catch (Exception e) { // 如果关闭流出现异常
                e.printStackTrace(); // 则打印异常
            }
        }
    }
    
    public static void main(String[] args) { // 测试程序
        Sheep sheep = new Sheep("silvery", 6, new Sheep("gold", 13, null));
        Sheep clone = sheep.clone();
        System.out.println(sheep == clone);
        System.out.println("[sheep]: name = " + sheep.getName() + ", age = " + sheep.getAge()
                + ", friend = " + sheep.getFriend() + ", friend.name = " + sheep.getFriend().getName());
        System.out.println("[clone]: name = " + clone.getName() + ", age = " + clone.getAge()
                + ", friend = " + clone.getFriend() + ", friend.name = " + clone.getFriend().getName());
        /* 测试结果如下:
            false
            [sheep]: name = silvery, age = 6, friend = cn.me.Sheep@421faab1, friend.name = gold
            [clone]: name = silvery, age = 6, friend = cn.me.Sheep@504bae78, friend.name = gold
            总结:
            sheep 和 name 不是同一个对象,但属性一样
            它们的 friend 不是同一个 friend,但属性一样,这是 深拷贝
         */
    }
}

2.2.2 特性

  • 深拷贝:这种方式是深拷贝,它会递归地构建所有对象,从而创建一个与原对象无任何关联的新对象,只是属性相同罢了。
  • 实现复杂:这种方式的实现很复杂,涉及到四个流的创建、使用和关闭,要处理的异常也很多。
  • 性能开销:这种方式由于需要创建、使用和关闭流,所以可能会消耗一部分性能。

3 实现的要点

  • 实现 Cloneable 接口。
  • 重写 Object 类的 clone() 方法。
  • 考虑深浅拷贝的问题。

4 Spring 中的原型模式

对于 ClassPathXmlApplicationContext 类的对象,它的 getBean() 方法继承自 AbstractBeanFactory 抽象类的 getBean() 方法,其内部调用了 doGetBean() 方法,在 doGetBean() 方法中使用了原型模式(这里面具体的逻辑就很复杂了),如下所示:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args,
		boolean typeCheckOnly) throws BeansException {
    // ...
    else if (mbd.isPrototype()) {
        prototypeInstance = null;

        Object prototypeInstance;
        try {
            this.beforePrototypeCreation(beanName);
            prototypeInstance = this.createBean(beanName, mbd, args); // 在这里使用了原型模式
        } finally {
            this.afterPrototypeCreation(beanName);
        }

        beanInstance = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    }
    // ...
}

5 原型模式的类图及角色

5.1 类图

5.1.1 不限制语言

alt text

5.1.2 在 Java 中的类图

alt text

5.2 角色

5.2.1 Prototype ( 原型 )

该角色负责 定义 用于复制现有实例来生成新实例的 方法,在 Java 中一般指的是 Object 类,它定义了 nativeclone() 方法。

5.2.2 ConcretePrototype ( 具体的原型 )

该角色负责 实现 用于复制现有实例来生成新实例的 方法,在 Java 中不仅要继承 Object 类(如果一个类不指定继承的类,则它默认继承 Object 类),还要实现 Cloneable 这个 标记接口,虽然它没有定义方法,但要使用 clone() 方法就得实现它,否则就会抛出 CloneNotSupportedException (不支持 clone() 方法)的异常。

5.2.3 Client ( 使用者 )

该角色负责 使用 用于复制现有实例来生成新实例的 方法

6 原型模式的优缺点

优点

  • 性能高:使用原型模式复用的方式创建实例对象,比使用构造器重新创建对象性能要高。
  • 流程简单:原型模式可以简化创建的过程,可以直接修改现有的对象实例的值,达到复用的目的。
  • 具有动态性:使用构造器创建对象的代码在运行期间时固定的,而使用原型模式可以获取对象运行时的属性,从而 动态地 创建出新的对象。

缺点

  • 实现复杂:必须重写对象的 clone() 方法,且需要考虑 深拷贝浅拷贝 的风险。
  • 克隆方法需要通盘考虑:配备克隆方法需要对类的功能进行通盘考虑,特别是对于已有的类,可能需要修改其结构以支持克隆,违背了 开闭原则

7 原型模式的使用场景

  • 对象的创建成本较高:如果创建对象的过程比较复杂或耗时较长,可以使用原型模式通过复制一个现有对象的属性和方法来创建新对象,从而避免昂贵的创建过程。
  • 需要创建大量相似的对象:如果需要创建大量相似的对象,可以先创建一个原型对象,然后通过复制原型对象来创建新对象,从而提高对象创建的效率和性能。
  • 对象的修改频繁:如果对象的属性需要经常变化,而且每次变化都需要创建一个新的对象,可以使用原型模式,通过复制原型对象来创建新对象并修改其属性,而不需要每次都重新创建新对象。
  • 隐藏对象的创建细节:如果创建对象的细节比较复杂,不希望客户端直接与创建对象的过程耦合,可以使用原型模式,客户端只需要通过复制一个已有对象来创建新对象,而无需知道创建的细节。

8 总结

原型模式是一种非常有用的 创建型 设计模式,它 通过复制已有的对象来创建新对象,从而 提高了对象创建的效率和性能。然而,在使用时需要注意 深浅拷贝问题 以及 克隆方法的实现细节,以避免出现不必要的错误。

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

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

相关文章

Java之集合底层-数据结构

Java集合之数据结构 1 概述 数据结构是计算机科学中研究数据组织、存储和操作的一门学科。它涉及了如何组织和存储数据以及如何设计和实现不同的数据操作算法和技术。常见的据结构有线性数据结构&#xff08;含数组、链表、栈和队列等&#xff09;&#xff0c;非线性数据结构…

四、GD32 MCU 常见外设介绍(1)RCU 时钟介绍

系统架构 1.RCU 时钟介绍 众所周知&#xff0c;时钟是MCU能正常运行的基本条件&#xff0c;就好比心跳或脉搏&#xff0c;为所有的工作单元提供时间 基数。时钟控制单元提供了一系列频率的时钟功能&#xff0c;包括多个内部RC振荡器时钟(IRC)、一个外部 高速晶体振荡器时钟(H…

Meta发布最强AI模型,扎克伯格公开信解释为何支持开源?

凤凰网科技讯 北京时间7月24日&#xff0c;脸书母公司Meta周二发布了最新大语言模型Llama 3.1&#xff0c;这是该公司目前为止推出的最强大开源模型&#xff0c;号称能够比肩OpenAI等公司的私有大模型。与此同时&#xff0c;Meta CEO马克扎克伯格(Mark Zuckerberg)发表公开信&a…

力扣1792.最大平均通过率

力扣1792.最大平均通过率 每个班级加上一个人以后得通过率增量不同 将优先级最高的班级放队列顶&#xff0c;每次操作即可 class Solution {public:struct Radio{int pass;int total;//满足该条件 oth的优先级更高bool operator < (const Radio& oth)const{return (l…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.1架构基础

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…

Java习题二

一题目要求&#xff1a; 二具体代码&#xff1a; package three;import sun.util.resources.LocaleData;import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*;public class test {public static void main(String[] args) {String us…

php--高级文件绕过

&#x1f3bc;个人主页&#xff1a;金灰 &#x1f60e;作者简介:一名简单的大一学生;易编橙终身成长社群的嘉宾.✨ 专注网络空间安全服务,期待与您的交流分享~ 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ &#x1f34a;易编橙终身成长社群&#…

UCOS-III 任务调度接口(OSSched)详解

在实时操作系统uC/OS-III中&#xff0c;调度器是核心组件之一&#xff0c;它负责管理任务的执行顺序和优先级。本文将详细解析uC/OS-III内核中的调度函数OSSched。 OSSched函数简介 OSSched函数用于检查并确定是否有更高优先级的任务需要运行。该函数通常在任务级别代码中调用…

【快速逆向四/无过程/有源码】浙江工商职业技术学院 统一身份认证

逆向日期&#xff1a;2024.07.23 使用工具&#xff1a;Node.js 加密方法&#xff1a;RSAUtils 文章全程已做去敏处理&#xff01;&#xff01;&#xff01; 【需要做的可联系我】 AES解密处理&#xff08;直接解密即可&#xff09;&#xff08;crypto-js.js 标准算法&#xf…

使用 Socket和动态代理以及反射 实现一个简易的 RPC 调用

使用 Socket、动态代理、反射 实现一个简易的 RPC 调用 我们前面有一篇 socket 的文章&#xff0c;再之前&#xff0c;还有一篇 java动态代理的文章&#xff0c;本文用到了那两篇文章中的知识点&#xff0c;需要的话可以回顾一下。 下面正文开始&#xff1a; 我们的背景是一个…

掌握Rust:函数、闭包与迭代器的综合运用

掌握Rust&#xff1a;函数、闭包与迭代器的综合运用 引言&#xff1a;解锁 Rust 高效编程的钥匙函数定义与模式匹配&#xff1a;构建逻辑的基石高阶函数与闭包&#xff1a;代码复用的艺术迭代器与 for 循环&#xff1a;高效数据处理的引擎综合应用案例&#xff1a;构建一个简易…

最新App崩溃率出炉!这样的行业均值水平如何?

前不久发布的《2024 Q1 移动应用性能体验报告》(以下简称报告),公布了最新的App崩溃率行业均值。基于友盟覆盖的终端设备,观测启动次数和崩溃次数,《报告》综合计算得出iOS APP崩溃率0.21%,Android Java崩溃率0.22%、native 0.16%、ANR 0.53%。 作为国内领先的第三方全域数据智…

PyMol在Windows系统上的免费安装指南

PyMOL是一个强大的分子可视化工具&#xff0c;广泛应用于生物化学、分子生物学和材料科学等领域。对于需要在Windows系统上进行分子结构分析和可视化的用户来说&#xff0c;安装一个免费版本的PyMol至关重要。本文将提供详细的步骤&#xff0c;指导如何在Windows系统上免费安装…

有哪些好用的 AI 学术研究工具和科研工具?

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频百万播放量https://aitools.jurilu.com/ AI 应用其实分两个层面&#xff0c;第一是模型&#xff0c;第二是应用。现在很多模型厂家都是既做 toC 的对话应用&#xff0c;也做 t…

Jmeter性能测试进行参数化操作

在使用Jmeter进行性能测试中,Jmeter的基本操作是肯定要会的,除此之外,还需要会多并发压测配置线程,多用户并发参数化的设置等等技术.下面就给大家介绍一下这个方面的内容: 一.Jmeter单个请求部署 1.设置线程组 注意:如果要使用调度器,那么循环次数的”永远”的选项一定要记得…

MySQL-视图、存储过程和触发器

一、视图的定义和使用 视图是从一个或者几个基本表&#xff08;或视图&#xff09;导出的表。它与基本表不同&#xff0c;是一个虚表,视图只能用来查询。不能做增删改查(虚拟的表) 1.视图的作用 简化查询重写格式化数据频繁访问数据库过滤数据 2.创建视图 -- 创建视图 -- 语法…

计算机三级嵌入式笔记(二)——嵌入式处理器

目录 考点1 嵌入式处理器的结构类型 考点2 嵌入式处理器简介 考点3 ARM处理器概述 考点4 处理器和处理器核 考点5 ARM 处理器的分类 考点6 经典 ARM 处理器 考点7 ARM Cortex 嵌入式处理器 考点8 ARM Cortex实时嵌入式处理器 考点9 ARM Cortex 应用处理器 考点10 AR…

Python群体趋向性潜关联有向无向多图层算法

&#x1f3af;要点 &#x1f3af;算法模型图层节点和边数学定义 | &#x1f3af;算法应用于贝叶斯推理或最大似然优化概率建模的多图层生成模型 | &#x1f3af;算法结合图结构边和节点属性 | &#x1f3af;对比群体关联预测推理生成式期望最大化多图层算法 | &#x1f3af;使…

51单片机嵌入式开发:17、STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息

51单片机嵌入式开发 STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息 51单片机嵌入式开发STC89C52的嵌入式 遥控器 控制步进电机 转速 和 转向 操作并 printf打印信息1 概述2 硬件电路2.1 遥控器2.2 红外接收器电路2.3 STC89C52单片机电路2.4 数码管…

工程视角:数据结构驱动的应用开发--字典(dictionary),列表(list)与实体

这里写目录标题 业务业务场景流程分析 实现数据访问层&#xff08;DAL&#xff09;业务逻辑层&#xff08;BLL&#xff09;用户界面层&#xff08;UI&#xff09;工具类 设计思路为什么抽出工具类关于U层使用字典的好处工程视角 业务 业务场景 在一个金融应用系统中&#xff0c…