创建型设计模式-4.原型设计模式

创建型设计模式-4.原型设计模式

原型设计模式(Prototype Design Pattern)是一种创建型设计模式,旨在通过复制现有对象来创建新对象,而不是通过使用构造函数进行创建。它允许我们通过克隆(复制)现有对象的实例来创建新的对象,而无需显式地依赖于特定类的构造函数。

主要分为:浅拷贝和深拷贝

1、浅拷贝

浅拷贝是原型设计模式中的一种复制方式,它复制对象内的所有基本数据类型和引用数据类型的地址。这意味着在浅拷贝中,原始对象和复制对象将共享相同的引用数据类型的实例。

当执行浅拷贝时,如果对象内有引用类型的成员变量,那么复制的对象将包含对原始对象引用数据类型成员变量的引用。这意味着两个对象的引用类型成员变量指向相同的对象,任何对引用类型的修改将会影响到两个对象。

好的!让我们使用Java来举一个例子,使用浅拷贝来创建歌曲和歌单对象。

首先,我们定义一个歌曲(Song)类:

public class Song {
    private String title;
    private String artist;

    public Song(String title, String artist) {
        this.title = title;
        this.artist = artist;
    }

    public String getTitle() {
        return title;
    }

    public String getArtist() {
        return artist;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public void setArtist(String artist) {
        this.artist = artist;
    }

    @Override
    public String toString() {
        return "Song: " + title + " - " + artist;
    }
}

接下来,我们定义一个歌单(Playlist)类,它包含了多个歌曲对象:

public class PlayList {
    private String name;
    private List<Song> songs;

    public PlayList(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    public String getName() {
        return name;
    }

    public List<Song> getSongs() {
        return songs;
    }

    public void addSong(Song song) {
        songs.add(song);
    }

    public void removeSong(Song song) {
        songs.remove(song);
    }

    public PlayList shallowCopy(String newName) {
        PlayList copy = new PlayList(newName);
        copy.songs = new ArrayList<>(songs);
        return copy;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("PlayList: ").append(name).append("\n");
        for (Song song : songs) {
            sb.append("- ").append(song).append("\n");
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        PlayList playList = new PlayList("歌单-全是redvelet的歌");
        playList.addSong(new Song("feel my rhythm", "redvelet"));
        playList.addSong(new Song("psycho", "redvelet"));
        playList.addSong(new Song("bad boy", "redvelet"));

        //创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
        PlayList newPlayList = playList.shallowCopy("歌单-kpop");

        System.out.println("playList = " + playList);
        System.out.println("newPlayList = " + newPlayList);

        //修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
        Song song = newPlayList.getSongs().get(2);
        song.setArtist("wendy");

        System.out.println("修改新歌单内的歌曲bad boy");
        System.out.println("playList = " + playList);
        System.out.println("newPlayList = " + newPlayList);
    }
}

Playlist类中,我们添加了一个shallowCopy()方法来执行浅拷贝。该方法会创建一个新的Playlist对象,并复制歌单中的歌曲列表。注意,我们使用了new ArrayList<>(songs)来复制歌曲列表的引用。

在这里插入图片描述


通过实现cloneable接口实现浅拷贝:

public class PlayListByCloneableInterface implements Cloneable, Serializable {
    private String name;
    private List<Song> songs;

    public PlayListByCloneableInterface(String name) {
        this.name = name;
        this.songs = new ArrayList<>();
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public List<Song> getSongs() {
        return songs;
    }

    public void addSong(Song song) {
        songs.add(song);
    }

    public void removeSong(Song song) {
        songs.remove(song);
    }

    public PlayListByCloneableInterface shallowCopy(String newName) {
        PlayListByCloneableInterface copy = new PlayListByCloneableInterface(newName);
        copy.songs = new ArrayList<>(songs);
        return copy;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("PlayList: ").append(name).append("\n");
        for (Song song : songs) {
            sb.append("- ").append(song).append("\n");
        }
        return sb.toString();
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        PlayListByCloneableInterface playList = new PlayListByCloneableInterface("歌单-全是redvelet的歌");
        playList.addSong(new Song("feel my rhythm", "redvelet"));
        playList.addSong(new Song("psycho", "redvelet"));
        playList.addSong(new Song("bad boy", "redvelet"));

        //创建一个新歌单,歌单包含原歌单playList1的所有歌曲,自己在新增歌曲
        PlayListByCloneableInterface newPlayList = (PlayListByCloneableInterface) playList.clone();
        newPlayList.setName("歌单-全是kpop的歌");
        System.out.println("playList = " + playList);
        System.out.println("newPlayList = " + newPlayList);

        //修改新歌单内的歌曲bad boy为人员为wendy,旧歌单的原信息会改变吗?
        Song song = newPlayList.getSongs().get(2);
        song.setArtist("wendy");

        System.out.println("修改新歌单内的歌曲bad boy");
        System.out.println("playList = " + playList);
        System.out.println("newPlayList = " + newPlayList);
    }
}

2、深拷贝

深拷贝是指在复制对象时,不仅复制对象本身,还复制对象所引用的所有子对象,使得复制后的对象与原始对象完全独立,互不影响

在进行深拷贝时,需要递归地复制对象及其子对象,确保每个子对象都是独立的副本,而不是共享引用。

深拷贝可以解决对象拷贝过程中可能出现的共享引用和副作用问题。它确保了复制对象与原始对象之间的数据隔离,使得修改复制后的对象不会影响原始对象,从而提高代码的可靠性和安全性。

在Java中,可以通过几种方式实现深拷贝:

  1. 递归复制:对于复杂对象,通过递归遍历对象的每个属性,并针对引用类型的属性进行深度复制。
  2. 序列化和反序列化:将对象序列化为字节流,然后再反序列化为新的对象。这种方式需要确保对象及其所有子对象都实现了 Serializable 接口。

递归复制:

class Product implements Cloneable {
    private String name;
    private double price;
    private int stock;
    // 省略构造函数、getter和setter方法
    @Override
    public Product clone() {
        try {
            return (Product) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
// 促销规则
class PromotionRule implements Cloneable {
    private String type;
    private double discount;
    private Product product;
    // 省略构造函数、getter和setter方法
    @Override
    protected PromotionRule clone() {
        try {
            PromotionRule promotionRule = (PromotionRule) super.clone()
                Product product = (Product)product.clone();promotionRule.setProduct(product);
            return promotionRule;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}
// 促销活动
class PromotionEvent implements Cloneable {
    private String name;
    private Date startDate;
    private Date endDate;
    private List<PromotionRule> rules;
    // 省略构造函数、getter和setter方法
    // 在促销活动中的clone方法需要克隆里边所有的非基础数据类型
    @Override
    protected PromotionEvent clone() {
        try {
            PromotionEvent clonedEvent = (PromotionEvent) super.clone();
            clonedEvent.startDate = (Date) startDate.clone();
            clonedEvent.endDate = (Date) endDate.clone();
            clonedEvent.rules = new ArrayList<>();
            for (PromotionRule rule : rules) {
                clonedEvent.rules.add(rule.clone());
            }
            return clonedEvent;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }
}

现在,我们已经为每个实体类实现了深拷贝方法。假设我们需要为不同的商品创建相似的促销活动,我们可以使用深拷贝来实现:

public class Main {
    public static void main(String[] args) {
        // 创建原始促销活动
        PromotionEvent originalEvent = createSamplePromotionEvent();
        // 创建新的促销活动
        PromotionEvent newEvent = originalEvent.clone();
        newEvent.setName("新的促销活动");
        // 现在newEvent是originalEvent的一个深拷贝副本,我们可以对它进行修改而不会影响originalEvent
        // 修改新促销活动的日期
        newEvent.setStartDate(addDays(newEvent.getStartDate(), 7));
        newEvent.setEndDate(addDays(newEvent.getEndDate(), 7));
        // 修改新促销活动的部分规则
        List<PromotionRule> newRules = newEvent.getRules();
        newRules.get(0).setDiscount(newRules.get(0).getDiscount() * 1.1);
        // 现在,我们已经成功地复制了一个与原始活动相似但具有不同日期和部分规则的新促销活动。
        // 可以将新活动应用于其他商品,而原始活动保持不变。
    }
    private static PromotionEvent createSamplePromotionEvent() {
        // 创建示例促销活动
        List<PromotionRule> rules = Arrays.asList(
            new PromotionRule("折扣", 0.9),
            new PromotionRule("满减", 50)
        );
        PromotionEvent event = new PromotionEvent(
            "原始促销活动",
            new Date(),
            addDays(new Date(), 7),
            rules
        );
        return event;
    }
    private static Date addDays(Date date, int days) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);calendar.add(Calendar.DATE, days);
        return calendar.getTime();
    }
}

序列化方式:前提需要实现Serializable接口

深拷贝的通用做法就是使用对象想对原型对象进行序列化,再对序列化后的二进制流 执行反序列化操作,就可以得到一个完完全全相同的对象,这种序列化的方式有很多,比如先转为json,在转成内存模型的对象,也是可以的。

@Test
public void deepCopyTest() throws Exception{
    User user = new User(12, "zhangsan");
    user.setDog(new Dog(2));
    
    // 将对象写到字节数组当中
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
    objectOutputStream.writeObject(user);
    // 获取字节数组
    byte[] bytes = outputStream.toByteArray();
    // 用输入流读出来
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
    Object object = objectInputStream.readObject();
    
    User user1 = (User) object;
    user.setAge(44);
    user.getDog().setAge(11);
    System.out.println(user);
    System.out.println(user1);
}

应用场景:

  1. 在订单管理系统中,深拷贝可以用于创建新订单并复制现有订单的商品和客户信息。假设我们有一个名为 Order 的类,其中包含订单信息和关联的商品和客户对象。我们可以通过深拷贝来创建新订单,并复制原始订单中的商品和客户信息,但需要重新填写新订单的其他信息。这样,新订单和原始订单是相互独立的对象,对新订单的修改不会影响原始订单。就比如每个月需要进一批货,但是大部分货的信息都一样,只是日期不一样,就可以使用深拷贝。

  2. 在前端开发中,重置按钮通常用于将表单或页面恢复到初始状态。当加载表单或页面时,可以进行深拷贝,复制一份初始内容作为参考。当用户点击重置按钮时,可以将深拷贝的对象作为引用,重新将其内容设置到表单或页面中,从而实现重置操作。通过深拷贝并使用引用转换,可以避免对原始对象的修改,确保每次重置都回到初始状态。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C++】开源:跨平台轻量日志库easyloggingpp

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍跨平台轻量日志库easyloggingpp。 无专精则不能成&#xff0c;无涉猎则不能通。。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&am…

【SQL应知应会】表分区(四)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 前言一、分区表1.非分区表2.分区…

Spring MVC异步上传、跨服务器上传和文件下载

一、异步上传 之前的上传方案&#xff0c;在上传成功后都会跳转页面。而在实际开发中&#xff0c;很多情况下上传后不进行跳转&#xff0c;而是进行页面的局部刷新&#xff0c;比如&#xff1a;上传头像成功后将头像显示在网页中。这时候就需要使用异步文件上传。 1.1 JSP页面 …

centos7 访问windows系统的共享文件夹

window系统上共享文件夹 选择共享文件夹&#xff0c;点击属性 点击共享 选择用户&#xff0c;点击共享 centos系统挂载共享文件夹 创建挂载目的文件夹 mkdir -p /mnt/smb 挂载命令 mount -t cifs -o usernamezenglg,password*** //192.168.1.10/ftp /mnt/smb/其中&#xff…

【计算机视觉 | 图像分割】arxiv 计算机视觉关于图像分割的学术速递(7 月 18 日论文合集)

文章目录 一、分割|语义相关(14篇)1.1 Multimodal Diffusion Segmentation Model for Object Segmentation from Manipulation Instructions1.2 Variational Probabilistic Fusion Network for RGB-T Semantic Segmentation1.3 Dense Affinity Matching for Few-Shot Segmentat…

Spring MVC拦截器和跨域请求

一、拦截器简介 SpringMVC的拦截器&#xff08;Interceptor&#xff09;也是AOP思想的一种实现方式。它与Servlet的过滤器&#xff08;Filter&#xff09;功能类似&#xff0c;主要用于拦截用户的请求并做相应的处理&#xff0c;通常应用在权限验证、记录请求信息的日志、判断用…

二十四章:SEgmentation TRansformer (SETR)——以Transformer的序列到序列的视角重新思考语义分割问题

0.摘要 最近的语义分割方法采用了全卷积网络&#xff08;FCN&#xff09;和编码器解码器架构。编码器逐渐降低空间分辨率&#xff0c;并学习具有更大感受野的抽象/语义视觉概念。由于上下文建模对于分割是至关重要的&#xff0c;最新的研究工作将重点放在增加感受野上&#xff…

Upgrading kubeadm clusters from v1.27.3 to v1.27.4

文章目录 1. Before you begin2. Notes3. Master3.1 Login into the first node and upgrade the kubeadm tool only3.2 Verify the upgrade plan3.3 Drain the control plane node3.4 kubeadm upgrade3.5 Uncordon the control plane node3.6 Upgrade kubelet and kubectl3.7 …

自然语言处理: 第五章Attention注意力机制

自然语言处理: 第五章Attention注意力机制 理论基础 Attention&#xff08;来自2017年google发表的[1706.03762] Attention Is All You Need (arxiv.org) &#xff09;&#xff0c;顾名思义是注意力机制&#xff0c;字面意思就是你所关注的东西&#xff0c;比如我们看到一个非…

【计算机组成原理】24王道考研笔记——第二章 数据的表示和运算

第二章 数据的表示和运算 一、数值与编码 1.1 进制转换 任意进制->十进制&#xff1a; 二进制<->八进制、十六进制&#xff1a; 各种进制的常见书写方式&#xff1a; 十进制->任意进制&#xff1a;&#xff08;用拼凑法最快&#xff09; 真值&#xff1a;符合人…

嵌入式软件和硬件的安全性:保护连接世界的数字盾牌

引言&#xff1a; 随着嵌入式系统的广泛应用和物联网的快速发展&#xff0c;嵌入式软件和硬件的安全性问题越来越引起人们的关注。安全性是确保嵌入式系统能够抵御恶意攻击和数据泄露的关键。本文将深入探讨嵌入式软件和硬件的安全性问题&#xff0c;包括技术原理、应用场景、学…

Edge 中比较独特的调试技巧

背景 大家日常开发基本都会使用 Chrome&#xff0c;毕竟确实好用。但是基于 Chromium 的新版 Microsoft Edge 已于 2020 年 1 月 15 日发布。 Edge 目前的使用基本跟 Chrome 差不多了&#xff0c;但显然&#xff0c;Edge 团队不仅仅想当 Chrome 的备用。他们也提供了一些特有…

【C++】-模板进阶(让你更好的使用模板创建无限可能)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

MySQL 8.0 OCP (1Z0-908) 考点精析-性能优化考点6:MySQL Enterprise Monitor之Query Analyzer

文章目录 MySQL 8.0 OCP (1Z0-908) 考点精析-性能优化考点6&#xff1a;MySQL Enterprise Monitor之Query AnalyzerMySQL Enterprise Monitor之Query AnalyzerQuery Response Time index (QRTi)例题例题1: Query Analyzer答案与解析1 参考 【免责声明】文章仅供学习交流&#x…

【本地电脑搭建Web服务器并用cpolar发布至公网

本地电脑搭建Web服务器并用cpolar发布至公网访问 随着互联网的快速发展&#xff0c;网络也成为我们生活中不可缺少的必要条件&#xff0c;为了能在互联网世界中有自己的一片天地&#xff0c;建立一个属于自己的网页就成为很多人的选择。但互联网行业作为资本密集的行业&#x…

一)Stable Diffusion使用教程:安装

目前AI绘画最火的当属Midjorney和Stable Diffusion&#xff0c;但是由于Midjourney没有开源&#xff0c;因此我们主要分享下Stable Diffusion&#xff0c;后面有望补上Midjourney教程。 本节主要讲解Stable Diffusion&#xff08;以下简述SD&#xff09;的下载和安装。 1&…

呼吸灯——FPGA

文章目录 前言一、呼吸灯是什么&#xff1f;1、介绍2、占空比调节示意图 二、系统设计1、系统框图2、RTL视图 三、源码四、效果五、总结六、参考资料 前言 环境&#xff1a; 1、Quartus18.0 2、vscode 3、板子型号&#xff1a;EP4CE6F17C8 要求&#xff1a; 将四个LED灯实现循环…

Redis源码篇 - Ziplist数据结构

Ziplist是一种内存优化的list存储结构&#xff0c;通过使用连续的内存空间存储&#xff0c;来减少内存碎片化&#xff0c;同时和链表的不同还有&#xff0c;它不存储前后指针&#xff0c;而是通过变长的字节存储前节点元素长度&#xff0c;通过计算长度来实现节点的查找。它是一…

Google 登录支付,Firebase 相关设置

登录sdk: https://developers.google.com/identity/sign-in/android/start?hlzh-cn 支付sdk: https://developers.google.com/pay/api/android/overview?hlzh-cn Firebase sdk: https://firebase.google.com/docs/android/setup?hlzh-cn 登录设置&#xff1a; 创建凭据&…

机器学习-线性代数-5-空间中的向量投影与最小二乘法

空间中的向量投影与最小二乘法 文章目录 空间中的向量投影与最小二乘法一、引入二、投影和投影的描述1、投影描述最近2、利用矩阵描述投影(1)向一维直线投影(2)向二维平面投影(3)向n维子空间投影的一般情况 三、最小二乘法1、重要的子空间(1)互补的子空间(2)正交的子空间(3)相互…