设计模式整理(4) 原型模式

学习《Android 源码设计模式解析与实践》系列笔记

介绍

原型模式是一种创建型的模式。
原型模式就是用户从一个样板实例对象中复制出一个内部属性一致的对象,这个过程也可以称作“克隆”。
原型模式多用于创建复杂的或者构造耗时的实例。

定义

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

使用场景

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

结构

设计模式整理(4) 原型模式_第1张图片
原型模式 UML 图
  • Client:使用者
  • Prototype:抽象类或者接口,声明具备 clone 能力
  • ConcretePrototype:具体的原型类

实现

以文档拷贝为例,文档中有文字和图片,用户经过了长时间编辑后,打算对文档进行一次较大改动,但是又怕修改的不满意,所以将当前的文本保存好,然后拷贝一份副本再修改。这里的原始文本就是样本实例,也就是被“克隆”的对象,也就是原型。

具体代码如下:

/**
 * 文档类型,也就是 UML 图中的 ConcretePrototype 类,而 Cloneable 是 Prototype。
 */
public class WordDocument implements Cloneable {

    private String mText; //文本
    private ArrayList mImages = new ArrayList<>(); // 图片列表

    public WordDocument() {
        System.out.println("WordDocument()");
    }

    public String getText() {
        return mText;
    }

    public void setText(String mText) {
        this.mText = mText;
    }

    public ArrayList getImages() {
        return mImages;
    }

    public void setImages(ArrayList mImages) {
        this.mImages = mImages;
    }

    public void addImage(String image) {
        if (mImages == null) {
            mImages = new ArrayList<>();
        }
        mImages.add(image);
    }

    @Override
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void showDocument() {
        System.out.println("----------------------------------");
        System.out.println("WordDocument:" + this);
        System.out.println("WordDocument mText:" + mText);
        System.out.println("WordDocument mImages:" + mImages);
        for (String image : mImages) {
            System.out.println("WordDocument image name:" + image);
        }
    }
}
public class Client {

    public static void main(String args []) {
        WordDocument orgDoc = new WordDocument();
        orgDoc.setText("document 1111");
        orgDoc.addImage("image 111");
        orgDoc.addImage("image 222");
        orgDoc.showDocument();

        WordDocument copyDoc = (WordDocument) orgDoc.clone();
        copyDoc.showDocument();
        copyDoc.setText("document 222");
        copyDoc.addImage("image 333");
        copyDoc.showDocument();

        orgDoc.showDocument();
    }

}

输出结果:

WordDocument()
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@1b6d3586
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222]
WordDocument image name:image 111
WordDocument image name:image 222
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@4554617c
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222]
WordDocument image name:image 111
WordDocument image name:image 222
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@4554617c
WordDocument mText:document 222
WordDocument mImages:[image 111, image 222, image 333]
WordDocument image name:image 111
WordDocument image name:image 222
WordDocument image name:image 333
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@1b6d3586
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222, image 333]
WordDocument image name:image 111
WordDocument image name:image 222
WordDocument image name:image 333

上面的例子,通过 WordDocument copyDoc = (WordDocument) orgDoc.clone(); 进行了副本的拷贝。
但是,从输出的 log 中,我们发现了几个问题:

  1. WordDocument 的构造函数只调用了一次,也就是说 orgDoc.clone() 生成 copyDoc 对象是不会调用构造函数的。
  2. 修改 copyDocmText 成员属性不会影响到 orgDocmText 成员属性,但是修改 mImages 后就会影响。这是因为 mText 是基础数据类型,拷贝的时候是值传递。mImages 是引用类型,我们这里的拷贝是浅拷贝,浅拷贝对引用类型的成员变量,只是拷贝了引用地址,它们指向的都是同一地址空间,所以任何一个对象修改了 mImages 的值,都会对另外一个产生影响。

关于浅拷贝和深拷贝,可以参考文章:
Java 浅拷贝和深拷贝

浅拷贝会带来数据安全方面的隐患,所以,我们进一步修改代码,达到深拷贝的目的。

这里主要是修改 WordDocumentclone() 方法,对引用类型的成员变量也进行一次拷贝。

/**
 * 文档类型,也就是 UML 图中的 ConcretePrototype 类,而 Cloneable 是 Prototype。
 */
public class WordDocument implements Cloneable {

    private String mText; //文本
    private ArrayList mImages = new ArrayList<>(); // 图片列表

    public WordDocument() {
        System.out.println("WordDocument()");
    }

    public String getText() {
        return mText;
    }

    public void setText(String mText) {
        this.mText = mText;
    }

    public ArrayList getImages() {
        return mImages;
    }

    public void setImages(ArrayList mImages) {
        this.mImages = mImages;
    }

    public void addImage(String image) {
        if (mImages == null) {
            mImages = new ArrayList<>();
        }
        mImages.add(image);
    }

    @Override
    public Object clone() {
        try {
            WordDocument document = (WordDocument) super.clone();
            document.mImages = (ArrayList) mImages.clone();
            return document;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void showDocument() {
        System.out.println("----------------------------------");
        System.out.println("WordDocument:" + this);
        System.out.println("WordDocument mText:" + mText);
        System.out.println("WordDocument mImages:" + mImages);
        for (String image : mImages) {
            System.out.println("WordDocument image name:" + image);
        }
    }
}

输出结果:

WordDocument()
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@1b6d3586
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222]
WordDocument image name:image 111
WordDocument image name:image 222
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@4554617c
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222]
WordDocument image name:image 111
WordDocument image name:image 222
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@4554617c
WordDocument mText:document 222
WordDocument mImages:[image 111, image 222, image 333]
WordDocument image name:image 111
WordDocument image name:image 222
WordDocument image name:image 333
----------------------------------
WordDocument:test.linpu.com.myapplication.prototype.WordDocument@1b6d3586
WordDocument mText:document 1111
WordDocument mImages:[image 111, image 222]
WordDocument image name:image 111
WordDocument image name:image 222

以上的输出结果可见,通过深拷贝和,修改 copyDocmImages 不会再影响到 orgDocmImages,因为此时的 mImages 是两个不同的对象。

源码中的原型模式

  1. ArrayList

如上述例子中可知,ArrayList 也是实现了 clone() 接口的。

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);
        }
    }
  1. Intent

Android 中,使用的较为多的 Intent 也是使用了原型模式。
其简单使用如下:

Uri uri = Uri.parse("tel:10086");
        Intent shareIntent = new Intent(Intent.ACTION_CALL, uri);
        // 克隆副本
        Intent copyIntent = (Intent) shareIntent.clone();

其源码如下:

@Override
    public Object clone() {
        return new Intent(this);
    }
public Intent(Intent o) {
        this(o, COPY_MODE_ALL);
    }
private Intent(Intent o, @CopyMode int copyMode) {
        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<>(o.mCategories);
        }

        if (copyMode != COPY_MODE_FILTER) {
            this.mFlags = o.mFlags;
            this.mContentUserHint = o.mContentUserHint;
            this.mLaunchToken = o.mLaunchToken;
            if (o.mSourceBounds != null) {
                this.mSourceBounds = new Rect(o.mSourceBounds);
            }
            if (o.mSelector != null) {
                this.mSelector = new Intent(o.mSelector);
            }

            if (copyMode != COPY_MODE_HISTORY) {
                if (o.mExtras != null) {
                    this.mExtras = new Bundle(o.mExtras);
                }
                if (o.mClipData != null) {
                    this.mClipData = new ClipData(o.mClipData);
                }
            } else {
                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
                    this.mExtras = Bundle.STRIPPED;
                }

                // Also set "stripped" clip data when we ever log mClipData in the (broadcast)
                // history.
            }
        }
    }

可见,Intent 的拷贝不是通过 super.clone() 来实现的,而是通过 new Intent(this),调用了其构造函数。

总结

原型模式的本质就是对象拷贝。拷贝分为浅拷贝和深拷贝。
原型模式主要有两个用途:

  1. 解决构建复杂对象的资源消耗问题,能够在某些场景提升创建对象的效率。
  2. 对原型对象进行保护。当某个对象对外可能是只读的,为了防止外部对这个只读对象修改,可以通过返回一个对象拷贝的形式实现只读的限制。



相关文章:
设计模式整理(1) 代理模式
设计模式整理(2) 单例模式
设计模式整理(3) Builder 模式
设计模式整理(4) 原型模式
设计模式整理(5) 工厂模式
设计模式整理(6) 策略模式
设计模式整理(7) 状态模式
设计模式整理(8) 责任链模式
设计模式整理(9) 观察者模式
设计模式整理(10) 适配器模式
设计模式整理(11) 装饰模式
设计模式整理(12) 中介者模式

你可能感兴趣的:(设计模式整理(4) 原型模式)