JAVA:深入理解原型模式构建可复用对象的关键技术

news2024/11/24 4:10:50

1、简述

在软件开发中,有时候我们需要创建许多相似但不完全相同的对象,这时候使用原型模式就显得非常有用。原型模式是一种创建型设计模式,它允许我们通过复制现有对象来创建新对象,而无需从头开始构建。本文将深入探讨 Java 中的原型模式,解释其原理、用法以及常见的应用场景。

在这里插入图片描述

2、原理

原型模式的核心思想是通过克隆(拷贝)现有对象来创建新的对象。这种克隆可以分为浅克隆和深克隆两种形式:

  • 浅克隆(Shallow Clone):只复制对象本身,而不复制对象的引用类型属性。即新对象和原对象共享引用类型属性。
  • 深克隆(Deep Clone):复制对象本身以及对象的引用类型属性,创建全新的对象,新对象和原对象的引用类型属性不共享。

Java 中实现原型模式的方式主要有两种:通过实现 Cloneable 接口和通过序列化。

3、浅拷贝

Java 提供了 Cloneable 接口,该接口表示一个类支持被复制(克隆)。如果一个类实现了 Cloneable 接口,那么它就可以使用 clone() 方法进行对象的克隆。下面是一个简单的示例:

public class Prototype implements Cloneable {
    private String data;
    /**
     * 引用Teacher 对象
     */
    private Teacher teacher;

    public Prototype(String data) {
        this.data = data;
    }

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

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}

引用的Teacher对象实例:

public class Teacher {
    private String data;

    public Teacher(String data) {
        this.data = data;
    }

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

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

通过使用main函数示例输出:

public class CloneMain {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher("Teacher Data");
        Prototype prototype = new Prototype("Original Data");
        teacher.setData("Teacher Data");
        prototype.setTeacher(teacher);
        
        Prototype clonedPrototype = prototype.clone();
        clonedPrototype.setData("Original Data1");
        clonedPrototype.getTeacher().setData("Teacher Data2");

        System.out.println("原来对象:" + prototype.getData()); // 输出:Original Data
        System.out.println("引用原来对象:" + prototype.getTeacher().getData()); // 输出:Teacher Data2

        System.out.println("拷贝对象:" + clonedPrototype.getData()); // 输出:Original Data1
        System.out.println("引用拷贝对象:" + clonedPrototype.getTeacher().getData()); // 输出:Teacher Data2
    }
}

通过执行控制台输出结果:

原来对象:Original Data
引用原来对象:Teacher Data2
拷贝对象:Original Data1
引用拷贝对象:Teacher Data2

观察以上运行结果,我们可以发现:在我们给Prototype 拷贝对象 修改老师的时候,Prototype 原型的老师也跟着被修改了。这就是浅拷贝带来的问题。

4、深拷贝

另一种实现深克隆的方法是通过序列化和反序列化。通过将对象写入输出流,然后再从输入流中读取对象,可以创建一个新的对象,这个过程中会创建对象的副本。下面是一个示例:

4.1 使用序列化和反序列化

通过将对象写入输出流,然后再从输入流中读取对象,可以创建一个新的对象,这个过程中会创建对象的副本。

import java.io.*;

public class DeepPrototype implements Serializable {
    private String data;
    /**
     * 引用Teacher 对象
     */
    private Teacher teacher;

    public DeepPrototype(String data) {
        this.data = data;
    }



    public DeepPrototype deepClone() throws IOException, ClassNotFoundException {
        // 写入输出流
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(this);

        // 从输入流中读取对象
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        return (DeepPrototype) objectInputStream.readObject();
    }

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public Teacher getTeacher() {
        return teacher;
    }

    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
}

引用的Teacher对象实例也要实现序列化Serializable:

public class Teacher implements Serializable {
    private String data;

    public Teacher(String data) {
        this.data = data;
    }

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

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }
}

通过使用main函数示例输出:

public class CloneMain {
    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Teacher teacher = new Teacher("Teacher Data");
        DeepPrototype prototype = new DeepPrototype("Original Data");
        teacher.setData("Teacher Data");
        prototype.setTeacher(teacher);

        DeepPrototype clonedPrototype = prototype.deepClone();
        clonedPrototype.setData("Original Data1");
        clonedPrototype.getTeacher().setData("Teacher Data2");

        System.out.println("原来对象:" + prototype.getData()); // 输出:Original Data
        System.out.println("引用原来对象:" + prototype.getTeacher().getData()); // 输出:Teacher Data

        System.out.println("拷贝对象:" + clonedPrototype.getData()); // 输出:Original Data2
        System.out.println("引用拷贝对象:" + clonedPrototype.getTeacher().getData()); // 输出:Teacher Data2
    }
}

通过执行控制台输出结果:

原来对象:Original Data
引用原来对象:Teacher Data
拷贝对象:Original Data1
引用拷贝对象:Teacher Data2
4.2 使用 JSON 序列化和反序列化

将对象转换为 JSON 格式的字符串,然后再从字符串中反序列化出对象。这种方法需要使用一个 JSON 库,如 Jackson、Gson 等。以下是一个示例:

import com.fasterxml.jackson.databind.ObjectMapper;

public class DeepCopyUtils {
    private static final ObjectMapper objectMapper = new ObjectMapper();

    public static <T> T deepCopy(T obj, Class<T> clazz) throws IOException {
        String json = objectMapper.writeValueAsString(obj);
        return objectMapper.readValue(json, clazz);
    }
}

4.3 使用 BeanUtils

Apache Commons BeanUtils 库提供了 BeanUtils.cloneBean() 方法,可以复制对象的属性到新对象中。这种方法需要确保对象的属性都是可序列化的。以下是一个示例:

import org.apache.commons.beanutils.BeanUtils;

public class DeepCopyUtils {
    public static <T> T deepCopy(T obj) throws ReflectiveOperationException {
        return (T) BeanUtils.cloneBean(obj);
    }
}

你可以使用 DeepCopyUtils.deepCopy() 方法来实现深拷贝。

5、应用场景

原型模式在开源框架中的应用也非常广泛。例如 Spring 中,@Service 默认都是单例的。用了私有全局变量,若不想影响下次注入或每次上下文获取 bean,就需要用到原型模式,我们可以通过以下注解来实现,@Scope(“prototype”)。

  • 当对象的创建成本较高,但需要频繁创建相似对象时,可以使用原型模式。
  • 当需要避免构造函数重复调用,而又需要获得新对象时,可以使用原型模式。
  • 当对象的结构比较复杂,无法直接使用 new 关键字创建对象时,可以使用原型模式。

6、总结

原型模式是一种创建型设计模式,通过复制现有对象来创建新对象,能够有效地减少对象的创建成本,提高代码的可复用性和可维护性。在 Java 中,可以通过实现 Cloneable 接口或者序列化来实现原型模式。通过深入理解原型模式的原理和用法,我们可以更好地应用它解决实际的软件设计和开发问题。

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

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

相关文章

银河麒麟桌面版操作系统修改主机名

1图形化方式修改 1.1在计算机图标上右键&#xff0c;选择属性 1.2修改 1.2.1点击修改计算机名 选择玩属性后会自动跳转到关于中&#xff0c;在计算机名中点击修改图标本质就是设置里面的系统下的关于&#xff0c;我们右键计算机选择属性就直接跳转过来了 1.2.2修改系统名字 …

day05-进程通信

1> 将互斥机制的代码实现重新敲一遍 代码&#xff1a; #include<myhead.h>int num520;//临界资源//1.创建互斥锁 pthread_mutex_t fastmutex;//定义任务函数 void *task1(void *arg){printf("1111111\n");//3.临界区上面获取锁资源&#xff08;上锁&#…

【嵌入式学习】QT-Day2-Qt基础

1> 思维导图 https://lingjun.life/wiki/EmbeddedNote/20QT 2>登录界面优化 使用手动连接&#xff0c;将登录框中的取消按钮使用qt4版本的连接到自定义的槽函数中&#xff0c;在自定义的槽函数中调用关闭函数 将登录按钮使用qt5版本的连接到自定义的槽函数中&#xff…

并发编程之深入理解Java线程

并发编程之深入理解Java线程 线程基础知识 线程和进程 进程 程序由指令和数据组成、但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须要将指令加载至CPU、数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理IO的…

项目解决方案:校园云视频平台方案(视频接入、汇聚、联网、分享)

目 录 一、项目需求 二、系统设计方案 三、平台功能 四、案例展示 本方案分四个部分&#xff1a;项目需求、系统设计方案、平台基础功能、案例展示&#xff0c;如下&#xff1a; 一、项目需求 二、系统设计方案 通过AS-V1000视频资源综合管理平台实现监控视频的接入、…

成年人学英语其实有个捷径,但你们都不信

上班了…… 我不想上班&#xff0c;只想躺平&#xff0c;同时银行卡上的余额还能够不断的增加。 当然现阶段肯定是不行的&#xff0c;我仍要靠打工养活自己&#xff0c;而且先要获得第一桶金。 第一桶金在何方&#xff1f;我还不知道&#xff0c;人在迷茫时&#xff0c;就来学英…

docker:Haoop集群

系列文章目录 docker&#xff1a;环境安装 docker:Web迁移 docker:Haoop集群 文章目录 系列文章目录前言一、宿主机选择二、环境准备1.前置技术2.网络环境1. docker网卡2. 分配IP 三、容器互联三、Jdk和Hadoop安装四、分发脚本五、启动Hadoop总结 前言 年前学习了docker的相关…

新疆营盘古城及古墓群安防舱体实施方案

3 总体布局 3.1设计原则 3.1.1执行有效的国家标准、国家军用标准和行业标准&#xff1b; 3.1.2满足指标要求&#xff1b; 3.1.3采用通用化、模块化设计&#xff0c;提高设备可维修性&#xff1b; 3.1.4采用人机工程学知识进行设计&#xff0c;充分考虑安全性。 3.2 总体…

Sora的第一波受害者出现了。

不知道大家最近除了被Sora刷屏之外&#xff0c;有没有被这张图刷屏 我只能说网友太强大了 说实话&#xff0c;我进入舟老师的直播间&#xff0c;每次都是还有3分钟下播&#xff0c;还有6单就拍完 但是10分钟后还在激情逼单&#xff0c;6单之后还有6单 也许在营销学上&#x…

亚马逊工程师严选,超 40 篇 LLM 论文汇总

2023 年&#xff0c;大语言模型依旧是「话题制造机」&#xff0c;不管是 OpenAI 的「宫斗剧」&#xff0c;还是各个大厂的新模型、新产品「神仙打架」&#xff0c;亦或是行业大模型发展的风生水起&#xff0c;都昭示着大语言模型具备巨大的发展空间。花香自引蝶&#xff0c;其实…

LeetCode 算法题 (数组)存在连续3个奇数的数组

问题&#xff1a; 输入一个数组&#xff0c;并输入长度&#xff0c;判断数组中是否存在连续3个元素都是奇数的情况&#xff0c;如果存在返回存在连续3个元素都是奇数的情况&#xff0c;不存在返回不存在连续3个元素都是奇数的情况 例一&#xff1a; 输入&#xff1a;a[1,2,3…

探索Django路由规则(路由匹配、路由命名空间、HTML中的跳转与Django集成、路由传参以及后端重定向)

路由管理&#xff1a; ​ 在实际开发过程中&#xff0c;⼀个Django 项⽬会包含很多的 app &#xff0c;这时候如果我们只在主路由⾥进⾏配置就会显得杂乱⽆章&#xff0c;所以通常会在每个 app ⾥&#xff0c;创建各⾃的 urls.py 路由模块&#xff0c;然后从根路由出发&#x…

leetcode日记(32)字符串相乘

做了很久很久……真的太繁琐了&#xff01;&#xff01; class Solution { public:string multiply(string num1, string num2) {string s;string str;if (num1 "0" || num2 "0") return "0";for(int inum2.size()-1;i>0;i--){int c2num2[…

代码随想录算法训练营第三十八天|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯。

509. 斐波那契数 题目链接&#xff1a;斐波那契数 题目描述&#xff1a; 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始&#xff0c;后面的每一项数字都是前面两项数字的和。也就是&#xff1a; F(0) 0&#xff0c…

2024 高级前端面试题之 计算机通识(基础) 「精选篇」

该内容主要整理关于 计算机通识&#xff08;基础&#xff09; 的相关面试题&#xff0c;其他内容面试题请移步至 「最新最全的前端面试题集锦」 查看。 计算机基础精选篇 一、网络1.1 UDP1.2 TCP1.3 HTTP1.4 DNS 二、数据结构2.1 栈2.2 队列2.3 链表2.4 树2.5 堆 三、算法3.1 时…

阿里云配置服务器详细指南_2024年CPU内存带宽配置选择

阿里云服务器配置怎么选择&#xff1f;根据实际使用场景选择&#xff0c;个人搭建网站可选2核2G配置&#xff0c;访问量大的话可以选择2核4G配置&#xff0c;企业部署Java、Python等开发环境可以选择2核8G配置&#xff0c;企业数据库、Web应用或APP可以选择4核8G配置或4核16G配…

Sora热潮 | 暴雨AI服务器助推大模型向前发展

Sora全球爆火这事还有谁不知道吗&#xff1f; 2月16日&#xff0c; OpenAI发布了一条由视频大模型Sora所自动生成的视频&#xff0c;逼真的视觉效果让其在一夜之间“刷屏”。 一石激起千层浪&#xff0c;Sora的发布让科技从业者&#xff0c;投资圈、影视行业纷纷“炸锅“&…

【双指针】:LCR179.查找总价值为目标值的两个商品

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

数据结构D3作业

1. 2. 按位插入 void insert_pos(seq_p L,datatype num,int pos) { if(LNULL) { printf("入参为空&#xff0c;请检查\n"); return; } if(seq_full(L)1) { printf("表已满&#xff0c;不能插入\n"); …

Spring 类型转换、数值绑定与验证(二)—PropertyEditor与Conversion

Spring 中&#xff0c;属性类型转换是在将数值绑定到目标对象时完成的。例如在创建ApplicationContext 容器时&#xff0c;将XML配置的bean 转换成Java类型对象&#xff0c;主要是借助了PropertyEditor类&#xff0c;而在Spring MVC 的Controller的请求参数转化为特定类型时&am…