zoukankan      html  css  js  c++  java
  • 原型模式

    一、概述

    一般问题:有时系统中需要创建重复对象,而这些对象的构造函数比较复杂耗时。

    核心方案:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

    设计意图:每当说到创建一个对象实例,我们总是想到调用构造函数new一个实例;实际上除了凭空创造一个新实例,还可以通过已有实例克隆一个实例。克隆比new的效率更高,尤其是当构造函数复杂耗时,比如需要读取数据库。

    原型模式类图:


    二、应用实践

    (1)浅克隆与深克隆

    在Java中要想克隆一个对象,必须实现空接口Cloneable,如:

        /**
         * Cloneable是一个空接口(标记接口),是一个规范。但是如果要克隆这个类对象的话必须实现Cloneable接口
         */
        public static class ProtoTypeTest implements Cloneable{
            private int testInt = 0;
            private String testString = "hello";
            private TestObj mObj = new TestObj();
            public ProtoTypeTest(){}
    
            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone(); //这里不需要我们主动为每一个变量赋值,clone过程有底层方法直接复制内存实现
            }
        }
    
        public static class TestObj{
            public int objInt = 2;
            public TestObj(){}
        }

    其中mObj是引用数据类型属性,testInt和testString是基本数据类型属性,它们在克隆时的表现是有区别的。

    我们写一个测试demo:

        ProtoTypeTest proto1 = new ProtoTypeTest(); //创建一个ProtoTypeTest实例proto1
           try {
                ProtoTypeTest proto2 = (ProtoTypeTest) proto1.clone(); //根据proto1克隆一个ProtoTypeTest实例proto2
                proto2.testInt = -1; //改变proto2的三个属性值并对比proto1和proto2的属性值
                proto2.testString = "hi";
                proto2.mObj.objInt = -2;
                Log.i("proto","proto1=(" + proto1.testInt + "," + proto1.testString + "," + proto1.mObj.objInt + ")");
                Log.i("proto","proto2=(" + proto2.testInt + "," + proto2.testString + "," + proto2.mObj.objInt + ")");
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }

    输出结果:

    06-19 13:37:29.128 14682 14682 I proto   : proto1=(0,hello,-2)
    06-19 13:37:29.128 14682 14682 I proto   : proto2=(-1,hi,-2)

    我们发现修改proto2的前两个基本类型属性时,proto1的对应属性并没有一起被修改;但是修改proto2的第三个引用类型属性时,proto1对应的属性也发生了变化。

    原因是java的clone函数只是“浅克隆”,也就是仅对变量内存做复制,如果是引用变量,其内存指向是不变的。

    如果要做“深克隆”,即内存指向也克隆一份,需要在clone函数中单独对引用变量克隆,如下:

        /**
         * Cloneable是一个空接口(标记接口),是一个规范。但是如果要克隆这个类对象的话必须实现Cloneable接口
         */
        public static class ProtoTypeTest implements Cloneable{
            private int testInt = 0;
            private String testString = "hello";
            private TestObj mObj = new TestObj();
            public ProtoTypeTest(){}
    
            @Override
            protected Object clone() throws CloneNotSupportedException {
                ProtoTypeTest cloneProto = (ProtoTypeTest) super.clone(); //这里不需要我们主动为每一个变量赋值,clone过程有底层方法直接复制内存实现
                cloneProto.mObj = (TestObj) mObj.clone(); //这里对TestObj引用变量单独克隆,同时TestObj也需要实现Cloneable接口
                return cloneProto;
            }
        }
    
        public static class TestObj implements Cloneable{
            public int objInt = 2;
            public TestObj(){}
    
            @Override
            protected Object clone() throws CloneNotSupportedException {
                return super.clone();
            }
        }

    输出结果:

    06-19 14:21:58.506 11297 11297 I proto   : proto1=(0,hello,2)
    06-19 14:21:58.506 11297 11297 I proto   : proto2=(-1,hi,-2)

    这样proto2和proto1就完全独立分开,互不影响了。

    (2)通过序列化与反序列化实现深克隆

    除了上面的通过为每个引用变量单独克隆实现“深克隆”,还可以通过序列化和反序列化实现“深克隆”。

    首先,克隆对象和内部引用对象都需要实现序列化接口

        /**
         * 实现序列化接口
         */
        public static class ProtoTypeTest implements Serializable{
            private int testInt = 0;
            private String testString = "hello";
            private TestObj mObj = new TestObj();
            public ProtoTypeTest(){}
        }
    
        //实现序列化接口
        public static class TestObj implements Serializable{
            public int objInt = 2;
            public TestObj(){}
        }

    通过序列化实现“深克隆”

             ProtoTypeTest proto1 = new ProtoTypeTest(); //创建一个ProtoTypeTest实例proto1
                    try {
                //ProtoTypeTest proto2 = (ProtoTypeTest) proto1.clone(); //根据proto1克隆一个ProtoTypeTest实例proto2
    
                        //下面使用序列化和反序列化实现"深克隆"
                        //1、将s1对象序列化为一个数组
                        ByteArrayOutputStream bos = new ByteArrayOutputStream(); //通过ObjectOutputStream流将s1对象读出来给ByteArrayOutputStream流
                        ObjectOutputStream    oos = new ObjectOutputStream(bos);
                        oos.writeObject(proto1);
                        byte[] bytes = bos.toByteArray(); //ByteArrayOutputStream流将对象信息转成byte数组,这样byte数组里就包含了对象的数据
    
                        //2、将字节数组中的内容反序列化为一个Sheep对象
                        ByteArrayInputStream bis = new ByteArrayInputStream(bytes); //通过ByteArrayInputStream流读入bytes字节数组中数据,然后传给ObjectInputStream对象输入流
                        ObjectInputStream ois = new ObjectInputStream(bis);
                        ProtoTypeTest proto2 = (ProtoTypeTest) ois.readObject(); //通过ObjectInputStream返回一个Sheep对象
    
                        proto2.testInt = -1; //改变proto2的三个属性值并对比proto1和proto2的属性值
                        proto2.testString = "hi";
                        proto2.mObj.objInt = -2;
                        Log.i("proto","proto1=(" + proto1.testInt + "," + proto1.testString + "," + proto1.mObj.objInt + ")");
                        Log.i("proto","proto2=(" + proto2.testInt + "," + proto2.testString + "," + proto2.mObj.objInt + ")");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

    输出结果和(1)中的“深克隆”结果一样:

    06-19 14:54:24.503 28741 28741 I proto   : proto1=(0,hello,2)
    06-19 14:54:24.503 28741 28741 I proto   : proto2=(-1,hi,-2)

    通过序列化和反序列化实现“深克隆”,不再需要实现cloneable接口,也不再需要对每个引用变量单独clone了。

    (3)Android中的原型模式

    Android中也有很多用到原型模式的地方,如Intent

    public class Intent implements Parcelable, Cloneable {
    
        @Override
        public Object clone() {
            return new Intent(this);
        }
       /**
         * Copy constructor.
         */
        public Intent(Intent o) {
            this(o, COPY_MODE_ALL);
        }
    }

    通过查看源码,Intent的clone实际上还是调用了new构造函数并为各个属性重新赋值。尽管在赋值时对个别属性做了条件判断,但个人感觉这样写并没有多大性能提升,反而可能仅仅是为了让Intent支持clone而已。


    三、总结

    总结:原型模式是一种创建型设计模式,当类初始化需要消化非常多的资源,这个资源包括数据、硬件资源、权限访问等时,可以考虑使用原型模式。除了优化性能之外,原型模式还可以对原实例起到保护作用。

    用一句话表述原型模式:

    如果生一个太麻烦,那就克隆吧~

    优点:

    • 性能提高
    • 避免构造函数的约束

    缺点:

    • 如类已经存在,则需要改动原类代码
    • 必须实现Cloneable或序列化接口
  • 相关阅读:
    java 24
    java 24
    java 24
    java 24
    一个用httpPost,get访问外网接口,参数json,返回json的示例
    几个强大的oracle自带函数,可根据日期算年纪,根据数值匹配字段
    sql对日期的处理,一个存储过程示例
    一条sql,有分页,表合并查询,多表连接,用于oracle数据库
    后台返回data直接在页面转换
    JQuery的$和其它JS发生冲突的快速解决方法
  • 原文地址:https://www.cnblogs.com/not2/p/11050827.html
Copyright © 2011-2022 走看看