Android 设计模式之原型模式

作者: AntDream | 来源:发表于2017-10-25 12:15 被阅读360次

    在日常开发过程中时常需要用到设计模式,但是设计模式有23种,如何将这些设计模式了然于胸并且能在实际开发过程中应用得得心应手呢?和我一起跟着《Android源码设计模式解析与实战》一书边学边应用吧!

    设计模式系列文章

    今天我们要讲的是原型模式


    定义

    用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象

    使用场景

    • 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗
    • 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式
    • 一个对象需要供给其他对象访问,而且各个对象都需要修改其值时,可以拷贝多个对象供调用者访问,即保护性拷贝

    使用例子

    • 我们常用的Intent,ArrayList等
    • 登录模块中保存的用户信息类需要通过服务器更新用户信息,但是有很多地方需要调用,需要设置为对其他用到的模块只读,这个时候可以考虑用原型模式进行保护性拷贝

    实现

    2大角色

    • 抽象类或接口,声明具备clone的能力
    • 具体的原型类

    实现的要点

    • 实现Cloneable接口
    • 覆写Object的clone方法

    实现方式

    下面以文档拷贝的例子来演示一下简单的原型模式

    • 文档类
    public class WordDocument implements Cloneable {
        //文本
        private String text;
    
        //图片
        private ArrayList<String> images = new ArrayList<String>();
    
        public WordDocument() {
            System.out.println("---WordDocument 构造函数---");
        }
    
        @Override
        protected WordDocument clone() {
            try {
                WordDocument document = (WordDocument) super.clone();
                document.text = this.text;
                document.images = this.images;
                return document;
    
            }catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
        public String getText() {
            return text;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    
        public ArrayList<String> getImages() {
            return images;
        }
    
        public void addImage(String img) {
            images.add(img);
        }
    
        /**
         * 打印文档内容
         */
        public void showDocument() {
            System.out.println("---Word content start---");
            System.out.println("Text : " + text);
            System.out.println("images list : ");
            for (String name : images) {
                System.out.println("image name : " + name);
            }
            System.out.println("---Word content end---");
        }
    
    }
    
    • 上面模拟创建了一个文档,里面包含文字和图片。拷贝时通过clone方法生成新的文档,并把文字和图片都拷贝到新的文档中
    • 下面是测试代码
    WordDocument doc1 = new WordDocument();
    doc1.setText("这是一篇文档");
    doc1.addImage("图片1");
    doc1.addImage("图片2");
    doc1.addImage("图片3");
    doc1.showDocument();
    
    WordDocument doc2 = doc1.clone();
    doc2.showDocument();
    
    doc2.setText("这是修改过的文档");
    doc2.addImage("图片4");
    doc2.showDocument();
    
    doc1.showDocument();
    
    • 下面是测试的输出
    ---WordDocument 构造函数---
    ---Word content start---
    Text : 这是一篇文档
    images list : 
    image name : 图片1
    image name : 图片2
    image name : 图片3
    ---Word content end---
    ---Word content start---
    Text : 这是一篇文档
    images list : 
    image name : 图片1
    image name : 图片2
    image name : 图片3
    ---Word content end---
    ---Word content start---
    Text : 这是修改过的文档
    images list : 
    image name : 图片1
    image name : 图片2
    image name : 图片3
    image name : 图片4
    ---Word content end---
    ---Word content start---
    Text : 这是一篇文档
    images list : 
    image name : 图片1
    image name : 图片2
    image name : 图片3
    image name : 图片4
    ---Word content end---
    
    • 不知道大家看了上面的测试有什么发现?
    • 首先我们成功拷贝了原始的文档。但是当我们修改拷贝后的副文档时,原始的文档也改变,这显然是不行的。

    浅拷贝和深拷贝

    • 浅拷贝又叫影子拷贝,上面我们在拷贝文档时并没有把原文档中的字段都重新构造了一遍,而只是拷贝了引用,也就是副文档的字段引用原始文档的字段,这样的话修改副文档中的内容就会连原始文档也改掉了,这就是浅拷贝
    • 深拷贝就是在浅拷贝的基础上,对于引用类型的字段也要采用拷贝的形式,比如上面的images,而像String、int这些基本数据类型则没关系
    • 所以在运用原型模式时建议大家还是用深拷贝,下面我们把上面的浅拷贝改成深拷贝
    public class WordDocument implements Cloneable {
        //文本
        private String text;
    
        //图片
        private ArrayList<String> images = new ArrayList<String>();
    
        public WordDocument() {
            System.out.println("---WordDocument 构造函数---");
        }
    
        @Override
        protected WordDocument clone() {
            try {
                WordDocument document = (WordDocument) super.clone();
                document.text = this.text;
                //document.images = this.images;
                //改成深拷贝
                document.images = (ArrayList<String>) this.images.clone();
                return document;
    
            }catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    
        //省略其他代码
    
        /**
         * 打印文档内容
         */
        public void showDocument() {
            //省略打印
        }
    
    }
    

    此外还要注意的是,拷贝的时候不会调用构造函数!

    Intent中的原型模式

    @Override
    public Object clone() {
        return new Intent(this);
    }
    
    private Intent(Intent o, boolean all) {
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        this.mPackage = o.mPackage;
        this.mComponent = o.mComponent;
        if (o.mCategories != null) {
            this.mCategories = new ArraySet<String>(o.mCategories);
        }
    }
    
    • 从上面的源码中可以看出,intent的clone方法实际上是通过new的方法来实现的,并没有调用super.clone()。在实现原型模式时,使用clone方法还是new需要根据构造对象的成本来考虑,如果对象的构造成本比较高或者构造比较麻烦,那么使用clone()函数效率较高

    总结

    • 原型模式本质上就是对象拷贝,容易出现的问题是深拷贝和浅拷贝
    • 使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率
    • 原型模式还有一个用途就是保护性拷贝,也就是某个对象可能是只读的,为了防止外部对这个只读对象修改,通常可以返回一个对象拷贝的形式实现只读的限制
    • 原型模式需要注意构造函数不会执行的问题

    源码地址:https://github.com/snowdream1314/DesignPatternsExamples


    欢迎关注我的微信公众号,期待与你一起学习,一起交流,一起成长!


    AntDream

    相关文章

      网友评论

        本文标题:Android 设计模式之原型模式

        本文链接:https://www.haomeiwen.com/subject/eflqpxtx.html