Android源碼設計模式——原型模式

上篇博客學習了Build模式,本篇博客學習一下原型模式,其實說到原型模式,剛開始聽的時候一臉懵逼,但是學習完了之後,真的是感覺簡單。

原型模式介紹

原型模式是創建型模式的一種,其實就是想要以某一個對象爲“模版”,“複製”出相同的對象,也就是“克隆”出一摸一樣的對象。原型模式多用於需要構建複雜的對象時使用。因爲此時“複製”一個對象比創建new一個對象效率更高。

原型模式定義

用原型對象的實例執行創建對象的種類,並通過拷貝創建出新的對象

原型模式的使用場景

  1. 創建new一個對象需要消耗非常多的資源,這個資源包括數據,硬件的消耗等,通過複製拷貝的方式避免過的消耗資源
  2. 一個對象可能在多個地方使用,並且使用的地方都可能修改這個對象的屬性值,此時應該拷貝若干份對象,給不同的調用者使用
  3. 通過new創建對象需要複雜的數據準備或者數據設置,這時可以直接拷貝複製對象。

一般來說,我們實現原型模式的方法有兩種:

  1. 原型對象實現Cloneable接口,然後在clone方法內,調用父類的對象clone方法拷貝對象。
  2. 不使用Cloneable接口,自行在clone方法內處理邏輯

原型模式的簡單實現(實現Cloneable接口)

示例:假設現在我們正在寫一個文檔Document,文檔中包含圖片和文字,用戶寫了很長的文檔,此時需要修改裏面的東西,但是呢,我們又不確定修改編輯後的內容是否會被採用,此時我們就可以拷貝一份文檔Document,這就是典型的:原型模式。

原型

public class Document implements Cloneable{

    private String content; //內容

    private ArrayList<String> imageList = new ArrayList<>(); //圖片

    public Document() {
        Log.e("Document:", "構造方法");
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public List<String> getImageList() {
        return imageList;
    }

    public void addImage(String image) {
        this.imageList.add(image);
    }

    @Override
    protected Document clone() {
        Document documentCopy = null;
        try{
            documentCopy = (Document) super.clone();
            documentCopy.content = this.content;
            documentCopy.imageList = this.imageList;
        } catch (CloneNotSupportedException e) {

        }
        return documentCopy;
    }

    @Override
    public String toString() {
        String diver = "\n------------start-------------\n";
        String contentString = "content = " + this.content + " \n";
        String imageString = "";
        for(String imageValue : imageList) {
            imageString += "圖片:" + imageValue + " \n";
        }
        String end = "------------end-------------\n";
        Log.e("DocumentL:", diver + contentString + imageString + end);
        return diver + contentString + imageString + end;
    }
}

在原型Document中,我們實現了Cloneable接口,在clone方法內,拷貝了Document對象

        Document  document = new Document();
        document.setContent("我是內容");
        document.addImage("我是圖片1");
        document.addImage("我是圖片2");
        document.toString();

        //clone
        Document documentClone = document.clone();
        documentClone.setContent("我是修改後的內容");
        documentClone.addImage("我是圖片3");
        documentClone.toString();

        //再次打印原版
        document.toString();

在這裏插入圖片描述
拷貝的Document對象修改了文本內容,但是原型對象文本內容並沒有被修改。 但是呢,細心的同學可能發現了,拷貝的對象添加了一張圖片,而原型對象中的圖片也隨之被修改了(添加了)。這是爲什麼?

深拷貝和淺拷貝

出現上述問題的原因是因爲是屬於:淺拷貝。

關於深拷貝和淺拷貝,用C++的地址內存來說的話就是:不管是基本數據類型還是引用數據類型,都會在棧中分配一塊內存,基本數據類型在棧中的那塊內存存儲的直接是它的值,而引用數據類型在棧中存儲的是一個地址(相當於一個指針),而這個地址指向了堆中的真正的值。也就是說:引用數據類型真正的值是存放在堆內存中的。

淺拷貝:只用把自己賦值給新對象,而不管自己是什麼類型(引用數據類型還是基本數據類型);
深拷貝:如果是引用數據類型,就調用起自己的clone方法拷貝,如果是基本數據類型就直接賦值。

還記得我們最早學習Java的時候的:值傳遞和引用傳遞嗎?對,就是這個東西。我們的文本內容是:基本數據類型,而圖片對象是:引用數據類型。此時我們的引用數據類型就相當於是淺拷貝,也就是說:

//直接複製的是值
documentCopy.content = this.content;
//直接傳遞的是數組的引用
documentCopy.imageList = this.imageList;

知道原因後,我們修改clone方法如下:

    @Override
    protected Document clone() {
        Document documentCopy = null;
        try{
            documentCopy = (Document) super.clone();
            ArrayList<String> imageListClone = (ArrayList<String>) this.imageList.clone();
            documentCopy.content = this.content;
            documentCopy.imageList = imageListClone;
        } catch (CloneNotSupportedException e) {

        }
        return documentCopy;
    }

在這裏插入圖片描述
由於ArrayList類本身實現了Cloneable接口,所以可以直接調用其clone方法複製對象。此時打印結果正確。

原型模式的其它實現方式

Intent的使用相信是非常熟悉的,看如下代碼:

Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("smsto:123456"));
intent.putExtra("sms_body", "Hello world!");
Intent cloneIntent = (Intent) intent.clone();
startActivity(cloneIntent);
上述代碼是發送短信。clone了Intent對象,我們看下clone方法的源碼:
    @Override
    public Object clone() {
        return new Intent(this);
    }
   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.
            }
        }
    }

在clone的方法內,直接創建了Intent對象。而沒有使用拷貝的方法創建,因此我們可以知道,在實際項目中,我們要根據自身情況看是否要:創建對象。

源碼示例地址:
GitHub示例源碼
GitHub示例源碼
GitHub示例源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章