• 设计模式总结-原型模式


    什么是原型模式?

    原型模式是通过给出一个原型对象来指明所创建的对象的类型,然后使用自身实现的克隆接口来复制这个原型对象,该模式就是用这种方式来创建出更多同类型的对象。

    原型模式的优点?

    一个对象通过new创建的过程为:

    1. 在内存中开辟一块空间;
    2. 在开辟的内存空间中创建对象;
    3. 调用对象的构造函数进行初始化对象。

    而一个对象通过clone创建的过程为:

    1. 根据原对象内存大小开辟一块内存空间;
    2. 复制已有对象,克隆对象中所有属性值。

    相对new来说,clone少了调用构造函数。如果构造函数中存在大量属性初始化或大对象,则使用clone的复制对象的方式性能会好一些。
    原型模式使用的Object 类的 clone 方法是一个本地方法,它直接操作内存中的二进制流。特别是复制大对象时,性能的差别非常明显。

    原型模式的应用场景?

    在一些重复创建对象的场景下,我们就可以使用原型模式来提高对象的创建性能。比如下面这个案例中:

     for(Map.Entry<Group, List<WidgetDetailResp>> entry : respMap.entrySet()){
                WidgetGroupResp resp = new WidgetGroupResp();
                resp.setName(entry.getKey().getDesc());
                resp.setCollapse(entry.getKey().getCollapse());
                resp.setWidgetDetailList(entry.getValue());
                respList.add(resp);
            }
    

    原型模式的实现条件?

    实现 Cloneable 接口:Cloneable 接口与序列化接口的作用类似,它只是告诉虚拟机可以安全地在实现了这个接口的类上使用 clone 方法。在 JVM 中,只有实现了 Cloneable 接口的类才可以被拷贝,否则会抛出 CloneNotSupportedException 异常。
    重写 Object 类中的 clone 方法:在 Java 中,所有类的父类都是 Object 类,而 Object 类中有一个 clone 方法,作用是返回对象的一个拷贝。
    在重写的 clone 方法中调用 super.clone():默认情况下,类不具备复制对象的能力,需要调用 super.clone() 来实现。

    原型模式的主要特征就是使用 clone 方法复制一个对象。通常,有些人会误以为 Object a=new Object();Object b=a; 这种形式就是一种对象复制的过程,然而这种复制只是对象引用的复制,也就是 a 和 b 对象指向了同一个内存地址,如果 b 修改了,a 的值也就跟着被修改了。

    原型模式的具体实现:

    //Person类实现Cloneable接口
    class Person implements Cloneable{
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name= name;
        }
        //重写clone方法
        @Override
        public Person clone() {
            Person person = null;
            try {
                person = (Person) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return person;
        }
    
    }
    public class Test {
    
        public static void main(String args[]) {
            Person person = new Person();
            person.setName("hello");
    
            Person person2 = person.clone();
            person2.setName("world");
    
            System.out.println("人员1:" + person.getName());
            System.out.println("人员2:" + person2.getName());
        }
    }
    

    结果:

    人员1:hello
    人员2:world
    

    貌似原型模式已经实现成功,我们再对上面的案例做下修改:

    //Person类实现Cloneable接口
    class Person implements Cloneable {
    
        private String name;
    
        private Address address;
    
        private List<String> cellphone;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Address getAddress() {
            return address;
        }
    
        public List<String> getCellphone() {
            return cellphone;
        }
    
        public void setAddress(Address address) {
            this.address = address;
        }
    
        public void setCellphone(List<String> cellphone) {
            this.cellphone = cellphone;
        }
    
        //重写clone方法
        @Override
        public Person clone() {
            Person person = null;
            try {
                person = (Person) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return person;
        }
    }
    
    class Address implements Cloneable {
    
        private String province;
    
        private String city;
    
        public String getProvince() {
            return province;
        }
    
        public String getCity() {
            return city;
        }
    
        public void setProvince(String province) {
            this.province = province;
        }
    
        public void setCity(String city) {
            this.city = city;
        }
    
        //重写clone方法
        @Override
        public Address clone() {
            Address address = null;
            try {
                address = (Address) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return address;
        }
    }
    
    public class Test {
    
        public static void main(String args[]) {
            List<String> cellphoneList = Lists.newArrayList("1111111");
            Address address  = new Address();
            address.setCity("徐汇");
            address.setProvince("上海");
            Person person = new Person();
            person.setName("人员1");
            person.setCellphone(cellphoneList);
            person.setAddress(address);
    
    
            List<String> cellphoneList2 = Lists.newArrayList("22222222");
            Person person2 = person.clone();
            person2.setName("人员2");
            person2.getCellphone().addAll(cellphoneList2);
            person2.getAddress().setCity("杭州");
    
            System.out.println("人员1姓名:" + person.getName());
            System.out.println("人员1电话:" + person.getCellphone());
            System.out.println("人员1地址:" + person.getAddress().getCity());
    
            System.out.println("人员2姓名:" + person2.getName());
            System.out.println("人员2电话:" + person2.getCellphone());
            System.out.println("人员2地址:" + person2.getAddress().getCity());
        }
    }
    

    结果:

    人员1姓名:人员1
    人员1电话:[1111111, 22222222]
    人员1地址:杭州
    人员2姓名:人员2
    人员2电话:[1111111, 22222222]
    人员2地址:杭州
    

    发现只对人员2做修改,人员1的信息竟然也发生了变化,这就涉及浅拷贝和深拷贝的问题了。super.clone()方法会创建一个和当前对象相同类型的对象,并且初始化值和原对象相同。但是涉及引用类型,数组,容器等的话就不会做值拷贝,只会做引用拷贝,这一点需要特别注意。

    • 浅拷贝:我们只拷贝对象中的基本数据类型(8种),对于数组、容器、引用对象等都不会拷贝
    • 深拷贝:不仅能拷贝基本数据类型,还能拷贝那些数组、容器、引用对象等
      那么怎么实现深拷贝呢,实现方法也很简单。
      我们对上面的案例做如下修改。
      重写Person类的clone方法
    //实现引用类型的递归clone
        @Override
        public Person clone() {
            Person person = null;
            try {
                person = (Person) super.clone();
                person.address = this.address.clone();
                person.cellphone = (ArrayList)((ArrayList)this.cellphone).clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return person;
        }
    

    重新运行结果如下:

    人员1姓名:人员1
    人员1电话:[1111111]
    人员1地址:徐汇
    人员2姓名:人员2
    人员2电话:[1111111, 22222222]
    人员2地址:杭州
    

    其实深拷贝就是浅拷贝的递归实现,没有多深奥的地方。

    最后我们再来看开头应用场景中的那个案例,就可以优化成下面这样:

            WidgetGroupResp resp = new WidgetGroupResp();
            for(Map.Entry<Group, List<WidgetDetailResp>> entry : respMap.entrySet()){
                WidgetGroupResp groupResp = resp.clone();
                groupResp.setName(entry.getKey().getDesc());
                groupResp.setCollapse(entry.getKey().getCollapse());
                resp.setWidgetDetailList(entry.getValue());
                respList.add(resp);
            }
    

    到这里本文就基本结束了,是不是感觉很简单,却又在不经意间提升了系统的性能。
    后续我会继续更新我们系统中目前在用的设计模式,设计模式我个人感觉没必要强行去记。
    如果你理解其中的思想,自然而然的后续就会想到使用。
    下一篇准备写下装饰模式,正好最近做券支付相关有用到,感兴趣的同学可以关注下。

  • 相关阅读:
    IDEA插件Mybatis logs不打印Mybatis 或者 Mybatis -plus 的SQL日志
    JRebel启动报错,但不影响正常运行JRebel: ERROR Class 'org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor' could not be processed by .....
    自增运算符 ++
    赋值操作符 =
    逻辑操作符
    算术操作符
    变量类型
    打印,注释,空白
    Java开发环境的安装和配置
    java应用背景
  • 原文地址:https://www.cnblogs.com/laoyeye/p/13256082.html
Copyright © 2020-2023  润新知