美文网首页
Android设计模式—原型模式

Android设计模式—原型模式

作者: Utte | 来源:发表于2018-09-04 21:16 被阅读17次

    一、定义

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

    原型模式本质上就是对象拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。

    二、使用场景

    • 类初始化需要消耗非常多的资源,包括数据资源、硬件资源。
    • 通过new产生一个对象时需要非常繁琐的数据准备和权限访问。

    用clone()构造实例时不一定比new要快,所以在使用Cloneable接口时需要考虑。当然,原型模式也不一定非要实现Cloneable接口,也有别的实现方式。

    • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,即保护性拷贝。

    三、原型模式的实现

    1. 需要拷贝的类

    实现Cloneable接口,再去实现clone()方法,实现对象的克隆。

    public class WordDocument implements Cloneable {
        private String mText;
        private ArrayList<String> mImages = new ArrayList<>();
    
        public WordDocument() {
        }
    
        @Override
        public Object clone() {
            try {
                WordDocument document = (WordDocument) super.clone();
                document.mText = this.mText;
                document.mImages = this.mImages;
                return document;
            } catch (Exception ignored) {
            }
            return null;
        }
    
        public String getText() {
            return mText;
        }
    
        public void setText(String text) {
            mText = text;
        }
    
        public ArrayList<String> getImages() {
            return mImages;
        }
    
        public void addImage(String image) {
            mImages.add(image);
        }
    
        public void showDocument() {
            System.out.println(mText);
            for (String image : mImages) {
                System.out.println(image);
            }
        }
    
    }
    
    

    2. 克隆

    尝试一下使用,首先创建一个对象,之后克隆对象再输出。

    WordDocument document = new WordDocument();
    document.setText("lll");
    document.addImage("aaa");
    document.addImage("bbb");
    document.showDocument();
    
    WordDocument newDocument = (WordDocument) document.clone();
    newDocument.showDocument();
    
    newDocument.setText("new change");
    newDocument.showDocument();
    
    document.showDocument();
    

    输出结果就是这样,克隆出的newDocument输出和document一样,在修改text后输出,newDocument改变了,document没有改变。

    lll
    aaa
    bbb
    
    lll
    aaa
    bbb
    
    new change
    aaa
    bbb
    
    lll
    aaa
    bbb
    

    3. 深拷贝

    看似已经赋值成功了,但是修改输出,就会发现是存在问题的。向克隆出的对象中添加一个Image。

    WordDocument document = new WordDocument();
    document.setText("lll");
    document.addImage("aaa");
    document.addImage("bbb");
    document.showDocument();
    
    WordDocument newDocument = (WordDocument) document.clone();
    newDocument.showDocument();
    newDocument.setText("new change");
    newDocument.addImage("new");
    newDocument.showDocument();
    
    document.showDocument();
    

    输出会发现不仅是克隆出的对象有新Image,原对象也被添加了新Image。这就牵扯到一个深拷贝浅拷贝的概念了。

    lll
    aaa
    bbb
    
    lll
    aaa
    bbb
    
    new change
    aaa
    bbb
    new
    
    lll
    aaa
    bbb
    new
    

    newDocument并不是将原始对象的所有字段都拷贝了一份,而是直接引用了原始对象的字段。这一句使newDocument的mImages集合指向原对象的mImages,所以在对新对象的集合添加元素时,实际上是在向原始对象集合中添加元素。

    document.mImages = this.mImages;
    

    将clone()修改如下,对象的mImages字段也进行clone()。

    @Override
    public Object clone() {
        try {
            WordDocument document = (WordDocument) super.clone();
            document.mText = this.mText;
            document.mImages = (ArrayList<String>) mImages.clone();
            return document;
        } catch (Exception ignored) {
        }
        return null;
    }
    

    输出如下,就会发现对newDocument添加image时,原始对象不会发生变化。

    lll
    aaa
    bbb
    
    lll
    aaa
    bbb
    
    new change
    aaa
    bbb
    new
    
    lll
    aaa
    bbb
    

    所以为了避免会出现上述问题,最好在实现原型模式时尽量使用深拷贝。

    在上面实现深拷贝时调用了ArrayList的clone(),顺便来看一下这个实现。

    transient Object[] elementData;
    private int size;
    
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
    

    首先克隆了自身,再克隆了ArrayList中实际存储数据的数组elementData。由于size是基本类型,并不是对象,所以不需要再克隆。克隆完成后直接返回。

    四、Intent中的原型模式

    Intent也实现了clone(),可以这样使用。

    Intent intent = new Intent(this, MainActivity.class);
    startActivity((Intent) intent.clone());
    

    clone()的实现

    很出乎意料的是Intent的clone()并没有调用super.clone()。而是调用了构造器并将当前原始Intent作为参数传入。

    @Override
    public Object clone() {
        return new Intent(this);
    }
    

    o为原始Intent。

    public Intent(Intent o) {
        this(o, COPY_MODE_ALL);
    }
    

    一直调用到了这个构造器,看到这个拷贝过程完全没有调用clone(),而是使用new的方法拷贝Intent对象的字段。

    private Intent(Intent o, @CopyMode int copyMode) {
        // 将原始Intent的字段赋值给新Intent
        this.mAction = o.mAction;
        this.mData = o.mData;
        this.mType = o.mType;
        // ...
        if (o.mCategories != null) {
            // 这里重新创建了一个内容一样的ArraySet,深拷贝
            this.mCategories = new ArraySet<>(o.mCategories);
        }
        if (copyMode != COPY_MODE_FILTER) {
            // 继续赋值
            this.mFlags = o.mFlags;
            this.mContentUserHint = o.mContentUserHint;
            this.mLaunchToken = o.mLaunchToken;
            // 都是通过new的方式达到深拷贝
            if (o.mSourceBounds != null) {
                this.mSourceBounds = new Rect(o.mSourceBounds);
            }
            // ...
        }
    }
    

    所以实现拷贝并不只有clone()一种实现方式,具体使用clone()还是使用new需要看实际情况。当new的成本不高的时候反而会比clone()效率高。

    五、小结

    • 深拷贝和浅拷贝是原型模式容易出现问题的地方。
    • 保护性拷贝是一个重要的用途。

    优点

    原型模式是对内存中二进制流的拷贝,在某些情况下比直接new一个对象的性能要好很多。

    缺点

    因为是直接在内存中拷贝,所以不会执行构造函数,在实际开发中应该注意这个潜在的问题。

    相关文章

      网友评论

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

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