• 设计模式之--原型模式(深浅拷贝)


    原型模式

    缘起

    某天,小明的Leader找到小明:“小明啊,如果有个发简历的需求,就是有个简历的模板,然后打印很多份,要去一份一份展示出来,用编程怎么实现呢?”

    小明一听,脑袋里就有了思路,二十分钟后给了一版代码

    // 简历类
    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.sex = sex;
            this.age = age;
        }
    
        // 设置工作经历
        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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    客户端代码

    public static void main(String[] args) {
        Resume resume1 = new Resume("小明");
        resume1.setPersonalInfo("男", "22");
        resume1.setWorkExperience("2021-2023", "XX公司");
    
        Resume resume2 = new Resume("小明");
        resume2.setPersonalInfo("男", "22");
        resume2.setWorkExperience("2021-2023", "XX公司");
    
        Resume resume3 = new Resume("小明");
        resume1.setPersonalInfo("男", "22");
        resume1.setWorkExperience("2021-2023", "XX公司");
    
        resume1.display();
        resume2.display();
        resume3.display();
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Leader看后,说道:“挺好,这其实就是我当年手写简历时代的代码哈哈哈,三份简历需要实例化三次。你觉得这样会不会麻烦呢?如果二十份简历,你就要实例化二十次是不是;而且如果你写错了一个字,那你就要改20次,你可以这么写”

    public class Test {
        public static void main(String[] args) {
            Resume resume1 = new Resume("小明");
            resume1.setPersonalInfo("男", "22");
            resume1.setWorkExperience("2021-2023", "XX公司");
    
            Resume resume2 = resume1;
    
            Resume resume3 = resume1;
    
            resume1.display();
            resume2.display();
            resume3.display();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    “其实就是传递引用对象,而不是传值,这样做就如同是在resume2、resume3纸上是空白的,而将resume1上的内容粘贴到了resume2、resume3上面,你还有没有其他的方式能实现呢?比如emmm,Clone克隆”。

    原型模式

    忙活了好一会儿,小明找到了一个相关的设计模式–原型模式。

    原型模式(Prototype),用原型实例指定创建对象的种类,并且通过复制这些原型创建的对象。

    在这里插入图片描述

    原型模式其实就是从一个对象再创建另外一个可制定的对象,而且不需要知道任何的创建细节。

    看下基本原型模式的代码。

    • 原型类
    // 原型类
    public abstract class Prototype implements Cloneable{
        private String id;
        public Prototype(String id) {
            this.id = id;
        }
        public String getId() {
            return id;
        }
        @Override
        protected Object clone() {
            Object object = null;
            try {
                object = super.clone();
            } catch (CloneNotSupportedException e) {
                System.out.println("克隆异常");
            }
            return object;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 具体原型类
    public class ConcretePrototype extends Prototype {
    
        public ConcretePrototype(String id) {
            super(id);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 客户端调用
    ConcretePrototype p1 = new ConcretePrototype("123456");
    System.out.println("原型ID:" + p1.getId());
    
    ConcretePrototype p2 = (ConcretePrototype) p1.clone();
    System.out.println("克隆ID:" + p2.getId());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这样子只需要实例化一个对象,其他的类实例化时,只需要克隆这个对象即可。

    对于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.sex = sex;
            this.age = age;
        }
    
        // 设置工作经历
        public void setWorkExperience(String timeArea, String company) {
            this.timeArea = timeArea;
            this.company = company;
        }
    
        // 展示简历
        public void display() {
            System.out.println(this.name + " " + this.age + " " + this.age);
            System.out.println("工作经历 " + this.timeArea + " " + this.company);
        }
    
        @Override
        protected Resume clone() throws CloneNotSupportedException {
            return (Resume) super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 客户端调用
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Resume resume1 = new Resume("小明");
            resume1.setPersonalInfo("男", "22");
            resume1.setWorkExperience("2021-2023", "XX公司");
            Resume resume2 = resume1.clone();
            Resume resume3 = resume1.clone();
    
            resume1.display();
            resume2.display();
            resume3.display();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    // 结果如下
    小明 男 22
    工作经历 2021-2023 XX公司
    小明 男 22
    工作经历 2021-2023 XX公司
    小明 男 22
    工作经历 2021-2023 XX公司
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Leader看后点了点头,“一般在初始化的信息不发生变化的情况下,克隆就是最好的办法。这既隐藏了对象的创建细节,又对性能是大大的提高。不用重新初始化对象,而是动态获得对象运行时的状态。”

    Leader接着又问道:“别高兴太早了,你知道这种clone有什么弊端吗,或者说是需要注意的点呢”

    小明摇了摇头,Leader接着说:“你知道深浅拷贝吧,如果字段是值类型的,则对该字段逐位复制;如果是引用类型的则只复制引用,不复制引用的对象;因此,原始对象及其副本中的引用都是同一个对象”。

    “你先把工作经历单独抽离出来,然后用简历类使用它们。”

    小明不到十分钟,改完了。

    • 简历类
    // 简历类
    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.sex = sex;
            this.age = age;
        }
    
        // 设置工作经历
        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());
        }
    
        @Override
        protected Resume clone() throws CloneNotSupportedException {
            return (Resume) super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 工作经历类
    public class WorkExperience {
    
        private String timeArea;
        private String company;
    
        public String getTimeArea() {
            return timeArea;
        }
    
        public void setTimeArea(String timeArea) {
            this.timeArea = timeArea;
        }
    
        public String getCompany() {
            return company;
        }
    
        public void setCompany(String company) {
            this.company = company;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 客户端
    public class Test {
        public static void main(String[] args) throws CloneNotSupportedException {
            Resume resume1 = new Resume("小明");
            resume1.setPersonalInfo("男", "22");
            resume1.setWorkExperience("2021-2023", "XX公司");
    
            Resume resume2 = resume1.clone();
            resume2.setWorkExperience("2001-2003", "ABC集团");
    
            Resume resume3 = resume1.clone();
            resume2.setWorkExperience("2005-2007", "ABC公司");
    
            resume1.display();
            resume2.display();
            resume3.display();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    // 执行结果如下
    小明 男 22
    工作经历 2005-2007 ABC公司
    小明 男 22
    工作经历 2005-2007 ABC公司
    小明 男 22
    工作经历 2005-2007 ABC公司
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    “看明白了吧,一个原型,两个副本它们的,workExperience对象全都是同一个引用,所以你改一个,其他的全都变了,这就是浅复制了。而我需要它们的workExperience对象全都是复制的对象,不能相同。”

    简历深拷贝实现

    “实现这个其实很简单,就是你的被引用对象,也去实现Cloneable接口,实现clone()方法,然后在引用类中将它们处理下就行了,快去查下相关资料,实现一下试试”。

    小明半小时后,新的代码又出炉了。

    • WorkExperience工作经历类
    public class WorkExperience implements Cloneable{
    
        private String timeArea;
        private String company;
    
        @Override
        protected WorkExperience clone() throws CloneNotSupportedException {
            return (WorkExperience) super.clone();
        }
    
    	.....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 简历类
    // 简历类
    public class Resume implements Cloneable{
        private String name;
        private String sex;
        private String age;
    
        private WorkExperience work;
    
    	....
    
        @Override
        protected Resume clone() throws CloneNotSupportedException {
            // 处理引用的对象
            Resume r = (Resume) super.clone();
            r.work = this.work.clone();
            return r;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    再来测试下。

    小明 男 22
    工作经历 2021-2023 XX公司
    小明 男 22
    工作经历 2005-2007 ABC公司
    小明 男 22
    工作经历 2021-2023 XX公司
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    总结

    浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

    深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

  • 相关阅读:
    ROS 文件系统
    UV 裂解的生物素-PEG2-叠氮|CAS:1192802-98-4生物素接头
    springboot整合redis-sentinel哨兵模式集群(一)
    网络安全人才缺口超百万,如今的就业情况怎样?
    LeetCode刷题系列 -- 39. 组合总和
    K8S知识点(九)
    【Rust 基础篇】Rust高级函数:函数作为参数与返回值
    线搜索方法
    2024腾讯游戏安全技术竞赛-机器学习赛道
    linux-进程-execl族函数
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/134357792