• 设计模式之原型模式


    1.前言

    概念

    原型模式(Prototype Pattern)是用于创建重复的对象同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。工作原理是将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象拷贝自己来实现创建过程。

    使用原型模式非常简单,实现一个接口,重写一个方法即完成了原型模式。

    使用场景

    • 创建新对象成本较大,新的对象可以通过原型模式对已有对象进行复制来获得
    • 如果系统要保存对象的状态,做备份使用

    2.原型模式核心组成

    • Prototype: 声明克隆方法的接口,是所有具体原型类的公共父类,Cloneable接口
    • ConcretePrototype : 具体原型类
    • Client: 让一个原型对象克隆自身从而创建一个新的对象

    UML图

    请添加图片描述

    因为克隆比较常用,所以Java提供了Cloneable接口,所以我们并不需要去编写Prototype接口,只需要实现Cloneable接口即可。不过Cloneable实现的是浅拷贝,如果想实现深拷贝,我们可以使用Serializable 接口。下面我们会细说分别使用浅拷贝与深拷贝实现原型模式。

    3.浅拷贝与深拷贝

    基本类型与引用类型

    在Java中,基本类型有整数类型(long、int、short、byte),浮点类型(float、double),字符类型(char),布尔类型(boolean)。

    除此之外所有的类型都是引用类型

    从存储来说,基本类型与引用类型的区别在于:

    • 在方法中定义的非全局基本数据类型变量的具体内容是存储在栈中的
    • 引用数据类型变量,栈中存放的是其具体内容所在内存的地址,而其具体内容都是存放在堆中的

    浅拷贝

    在浅拷贝中:

    • 如果原型对象的成员变量是基本数据类型,将复制一份给克隆对象;
    • 如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象, 也就是说原型对象和克隆对象的成员变量指向相同的内存地址

    代码演示

    接下来我们使用Resume类来讲解。

    具体原型类

    package ProtoType.ShallowCopy;
    
    import java.util.List;
    
    /**
     * @Author: 少不入川
     * @Date: 2022/11/28 21:19
     */
    public class Resume implements Cloneable{
        private String name;
        private int age;
        private List<String> technologyStack;
    
        public Resume(){}
    
        public Resume(String name, int age, List<String> technologyStack){
            this.name = name;
            this.age = age;
            this.technologyStack = technologyStack;
        }
    
        public List<String> getTechnologyStack() {
            return technologyStack;
        }
    
        public void setTechnologyStack(List<String> technologyStack) {
            this.technologyStack = technologyStack;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Resume{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", technologyStack=" + technologyStack +
                    '}';
        }
    
        // 使用public修饰符方便调用
        @Override
        public Object clone(){
            Resume resume = null;
            try{
                resume = (Resume) super.clone();
            }catch (Exception e){
                e.printStackTrace();
            }
            return resume;
        }
    }
    
    • 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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    测试类

    package ProtoType.ShallowCopy;
    
    import java.util.ArrayList;
    
    /**
     * @Author: 少不入川
     * @Date: 2022/11/28 21:58
     */
    public class ShallowCopyTest {
        public static void main(String[] args) {
            Resume resume1 = new Resume("张三", 20, new ArrayList<>());
            Resume resume2 = (Resume) resume1.clone();
    
            // 引用型变量使用==判等判断的是引用型变量指向地址是否相同
            System.out.println("两个对象所指向内存地址是否相同:" + (resume1 == resume2));
            System.out.println("两个对象的成员变量name所指向的内存地址是否相同:" + (resume1.getTechnologyStack() == resume2.getTechnologyStack()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    控制台结果

    两个对象所指向内存地址是否相同:false
    两个对象的成员变量name所指向的内存地址是否相同:true
    
    • 1
    • 2

    深拷贝

    在深拷贝中:

    • 无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象

    代码演示

    具体原型类

    package ProtoType.DeepCopy;
    
    import java.io.*;
    import java.util.List;
    
    /**
     * @Author: 少不入川
     * @Date: 2022/11/28 21:58
     */
    public class Resume implements Serializable {
        private String name;
        private int age;
        private List<String> technologyStack;
    
        public Resume(){}
    
        public Resume(String name, int age, List<String> technologyStack){
            this.name = name;
            this.age = age;
            this.technologyStack = technologyStack;
        }
    
        public List<String> getTechnologyStack() {
            return technologyStack;
        }
    
        public void setTechnologyStack(List<String> technologyStack) {
            this.technologyStack = technologyStack;
        }
    
        public String getName() {
            return name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Resume{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", technologyStack=" + technologyStack +
                    '}';
        }
    
        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);
                Resume resume = (Resume) ois.readObject();
    
                return resume;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            } finally {
                try {
                    bos.close();
                    oos.close();
                    bis.close();
                    ois.close();
                } catch (Exception e2) {
                    System.out.println(e2.getMessage());
                }
            }
        }
    
    }
    
    • 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
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    测试类

    package ProtoType.DeepCopy;
    
    import java.util.ArrayList;
    
    /**
     * @Author: 少不入川
     * @Date: 2022/11/28 22:09
     */
    public class DeepCopyTest {
        public static void main(String[] args) {
    
            Resume resume1 = new Resume("张三", 20, new ArrayList<>());
            Resume resume2 = (Resume) resume1.deepClone();
    
            // 引用型变量使用==判等判断的是引用型变量指向地址是否相同
            System.out.println("两个对象所指向内存地址是否相同:" + (resume1 == resume2));
            System.out.println("两个对象的成员变量name所指向的内存地址是否相同:" + (resume1.getTechnologyStack() == resume2.getTechnologyStack()));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    控制台输出

    两个对象所指向内存地址是否相同:false
    两个对象的成员变量name所指向的内存地址是否相同:false
    
    • 1
    • 2

    4.原型模式的优点与缺点

    • 优点
      • 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,可以提高新实例的创建效率
      • 可辅助实现撤销操作,使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用恢复到历史状态
    • 缺点
      • 需要为每一个类配备一个克隆方法,对已有的类进行改造时,需要修改源代码,违背了“开闭原则”
  • 相关阅读:
    Matlab论文插图绘制模板第115期—带Latex公式的图
    结对编程 --- 大部分程序员喜欢的编程方式
    桂林电子科技大学计算机考研资料汇总
    Java 进阶多线程(一)
    Python抽象类和接口类
    基于ASRPRO智能离线语音识别模块实现人机交流对话应用
    [项目管理-31]:Scurm迭代周期的项目总结与项目计划
    怎么把图片改成jpg格式?
    GO-日志分析
    CH58X/CH57X/V208的Broadcaster(广播者)例程讲解
  • 原文地址:https://blog.csdn.net/qq_52002412/article/details/128089029