設(shè)計模式整理(4) 原型模式

學(xué)習(xí)《Android 源碼設(shè)計模式解析與實踐》系列筆記

介紹

原型模式是一種創(chuàng)建型的模式。
原型模式就是用戶從一個樣板實例對象中復(fù)制出一個內(nèi)部屬性一致的對象,這個過程也可以稱作“克隆”。
原型模式多用于創(chuàng)建復(fù)雜的或者構(gòu)造耗時的實例。

定義

用原型實例指定創(chuàng)建對象的種類,并通過拷貝這些原型創(chuàng)建新的對象。

使用場景

  1. 類初始化需要消化非常多的資源,這個資源包括數(shù)據(jù)、硬件資源等,通過原型拷貝避免這些消耗。
  2. 通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備貨訪問權(quán)限。
  3. 一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用,即保護(hù)性拷貝。

結(jié)構(gòu)

原型模式 UML 圖
  • Client:使用者
  • Prototype:抽象類或者接口,聲明具備 clone 能力
  • ConcretePrototype:具體的原型類

實現(xiàn)

以文檔拷貝為例,文檔中有文字和圖片,用戶經(jīng)過了長時間編輯后,打算對文檔進(jìn)行一次較大改動,但是又怕修改的不滿意,所以將當(dāng)前的文本保存好,然后拷貝一份副本再修改。這里的原始文本就是樣本實例,也就是被“克隆”的對象,也就是原型。

具體代碼如下:

/**
 * 文檔類型,也就是 UML 圖中的 ConcretePrototype 類,而 Cloneable 是 Prototype。
 */
public class WordDocument implements Cloneable {

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

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

    public String getText() {
        return mText;
    }

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

    public ArrayList<String> getImages() {
        return mImages;
    }

    public void setImages(ArrayList<String> 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();
    }

}

輸出結(jié)果:

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(); 進(jìn)行了副本的拷貝。
但是,從輸出的 log 中,我們發(fā)現(xiàn)了幾個問題:

  1. WordDocument 的構(gòu)造函數(shù)只調(diào)用了一次,也就是說 orgDoc.clone() 生成 copyDoc 對象是不會調(diào)用構(gòu)造函數(shù)的。
  2. 修改 copyDocmText 成員屬性不會影響到 orgDocmText 成員屬性,但是修改 mImages 后就會影響。這是因為 mText 是基礎(chǔ)數(shù)據(jù)類型,拷貝的時候是值傳遞。mImages 是引用類型,我們這里的拷貝是淺拷貝,淺拷貝對引用類型的成員變量,只是拷貝了引用地址,它們指向的都是同一地址空間,所以任何一個對象修改了 mImages 的值,都會對另外一個產(chǎn)生影響。

關(guān)于淺拷貝和深拷貝,可以參考文章:
Java 淺拷貝和深拷貝

淺拷貝會帶來數(shù)據(jù)安全方面的隱患,所以,我們進(jìn)一步修改代碼,達(dá)到深拷貝的目的。

這里主要是修改 WordDocumentclone() 方法,對引用類型的成員變量也進(jìn)行一次拷貝。

/**
 * 文檔類型,也就是 UML 圖中的 ConcretePrototype 類,而 Cloneable 是 Prototype。
 */
public class WordDocument implements Cloneable {

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

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

    public String getText() {
        return mText;
    }

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

    public ArrayList<String> getImages() {
        return mImages;
    }

    public void setImages(ArrayList<String> 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<String>) 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);
        }
    }
}

輸出結(jié)果:

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

以上的輸出結(jié)果可見,通過深拷貝和,修改 copyDocmImages 不會再影響到 orgDocmImages,因為此時的 mImages 是兩個不同的對象。

源碼中的原型模式

  1. ArrayList

如上述例子中可知,ArrayList 也是實現(xiàn)了 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() 來實現(xiàn)的,而是通過 new Intent(this),調(diào)用了其構(gòu)造函數(shù)。

總結(jié)

原型模式的本質(zhì)就是對象拷貝??截惙譃闇\拷貝和深拷貝。
原型模式主要有兩個用途:

  1. 解決構(gòu)建復(fù)雜對象的資源消耗問題,能夠在某些場景提升創(chuàng)建對象的效率。
  2. 對原型對象進(jìn)行保護(hù)。當(dāng)某個對象對外可能是只讀的,為了防止外部對這個只讀對象修改,可以通過返回一個對象拷貝的形式實現(xiàn)只讀的限制。



相關(guān)文章:
設(shè)計模式整理(1) 代理模式
設(shè)計模式整理(2) 單例模式
設(shè)計模式整理(3) Builder 模式
設(shè)計模式整理(4) 原型模式
設(shè)計模式整理(5) 工廠模式
設(shè)計模式整理(6) 策略模式
設(shè)計模式整理(7) 狀態(tài)模式
設(shè)計模式整理(8) 責(zé)任鏈模式
設(shè)計模式整理(9) 觀察者模式
設(shè)計模式整理(10) 適配器模式
設(shè)計模式整理(11) 裝飾模式
設(shè)計模式整理(12) 中介者模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容