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或序列化接口
  • 相关阅读:
    C. Shaass and Lights 解析(思維、組合)
    D. Binary String To Subsequences(队列)(贪心)
    CodeForces 1384B2. Koa and the Beach (Hard Version)(贪心)
    CodeForces 1384B1. Koa and the Beach (Easy Version)(搜索)
    CodeForces 1384C. String Transformation 1(贪心)(并查集)
    CodeForces 1384A. Common Prefixes
    POJ-2516 Minimum Cost(最小费用最大流)
    POJ3261-Milk Patterns(后缀数组)
    HDU-1300 Pearls(斜率DP)
    HDU-4528 小明系列故事-捉迷藏(BFS)
  • 原文地址:https://www.cnblogs.com/not2/p/11050827.html
Copyright © 2011-2022 走看看