Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> java/android 設計模式學習筆記(11)---原型模式

java/android 設計模式學習筆記(11)---原型模式

編輯:關於Android編程

這篇博客我們來介紹一下剩下的最後一個創建型模式:原型模式(Prototype Pattern)。該模式有一個樣板實例,用戶從這個樣板對象中復制出一個內部屬性一致的對象,這個過程在 C++ 中就是一個克隆。被復制的實例就是我們所稱的“原型”,這個原型是可定制的。原型模式多用於創建復雜的或者構造耗時的實例,因為這種情況下,復制一個已經存在的實例可以使程序運行效率更高。
  這個模式的重點在於,客戶端的代碼在不知道要實例化何種特定類的情況下,就可以制造出新的實例。

特點

用原型實例指向創建對象的種類,並通過拷貝這些原型創建新的對象。
  原型模式的適用場景:

類初始化需要消耗很多的資源,這個資源包括數據,硬件資源等,簡而言之,這個實例化的過程很昂貴時,就可以使用原型模式;頻繁的創建相似的對象時,比如需要在一個循環體內創建對象,假如對象創建過程比較復雜或者循環次數很多的話,使用原型模式不但可以簡化創建過程,而且可以使系統的整體性能提高很多;通過 new 產生一個對象需要非常繁瑣的數據准備或訪問權限,這時可以使用原型模式;一個對象需要提供給其他對象訪問,並且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用,即保護性拷貝;避免在客戶端調用一個對象子類的生成器,即和抽象工廠模式一樣,子類的生成器對客戶端不透明。  需要注意的,通過實現 Cloneable 接口的原型模式在調用 clone 函數構造實例並不一定比通過 new 操作速度快,只有通過 new 構造對象較為耗時或者說成本較高時,通過 clone 方法才能夠獲得效率上的提升。因此,在使用 Cloneable 時需要考慮構建對象的成本以及做一些效率上的測試,當然,實現原型模式也不一定非要實現 Cloneable 接口,也有其他的實現方式。

 

UML類圖

我們來看看原型模式的 uml 類圖:
  這裡寫圖片描述vcfJq6O6PC9wPg0KQ2xpZW50o7q/zbuntsvTw7uno7tQcm90b3R5cGWjurPpz/PA4Lvy1d+907/ao6zJ+cP3vt+xuCBjbG9uZSC1xMTcwaaju0NvbmNyZXRlUHJvdG90eXBlo7q+38zltcTUrdDNwOCho86qwcvKtc/W1K3QzcSjyr2jrMrXz8jSqsn5w/fSu7j20Om7+cDgu/LV373Tv9qjrLjDwODW0NPQ0ru49rWltL+1xNDpt723qCBjbG9uZaOsyM66ztK7uPbQ6NKqtuDMrLm51Oy6r8r90NTWyrXEwOC2vNDo0qq8zLPQ19S4w9DpwOC78tXfyrXP1rjDvdO/2qOssqLH0sq1z9YgY2xvbmUgt723qKO7yLu687/Nu6e2y7K71rG907X308PA4LXEubnU7Lqvyv2jrLb4yse199PD1K3QzcDgtcQgY2xvbmUgt723qNPDwLTJ+rPJwe3Su7j2ttTP86Osu/LV38q508PG5Mv7yei8xsSjyr3M4bmptcS5psTcwLS199PDIGNsb25lILe9t6ijrLHIyOe5pLOnxKPKvbXIoaM8YnIgLz4NCqGhoaG+3bTLztLDx77Nv8nS1NC0s/bUrdDNxKPKvbXEzajTw7T6wuujujxiciAvPg0KPHN0cm9uZz5Db25jcmV0ZVByb3RvdHlwZS5jbGFzczwvc3Ryb25nPg0KPHA+Jm5ic3A7PC9wPg0KPHByZSBjbGFzcz0="brush:java;"> public class ConcretePrototype implements Cloneable{ private String string; private ArrayList stringList; public ConcretePrototype() { stringList = new ArrayList<>(); } public String getString() { return string; } public void setString(String string) { this.string = string; } public ArrayList getStringList() { return stringList; } public void setStringList(ArrayList stringList) { this.stringList = stringList; } public ConcretePrototype clone() { try { ConcretePrototype copy = (ConcretePrototype) super.clone(); copy.setString(this.getString()); copy.setStringList((ArrayList) getStringList().clone()); return copy; } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }

我們直接使用系統的 Cloneable 接口來作為 Prototype 角色,接著重寫Object類中的clone方法。Java 中,所有類的父類都是 Object 類,Object 類中有一個 clone 方法,作用是返回對象的一個拷貝,但是其作用域 protected 類型的,一般的類無法調用,因此,Prototype 類需要將 clone 方法的作用域修改為 public 類型。測試代碼:

ConcretePrototype src = new ConcretePrototype();
src.setString("src");
src.getStringList().add("src 1");
src.getStringList().add("src 2");
ConcretePrototype des = src.clone();
des.setString("des");
des.getStringList().add("des 1");
des.getStringList().add("des 2");
Log.e("shawn", "src.string = " + src.getString() +"   des.string = " + des.getString());
StringBuilder builder = new StringBuilder();
for (String temp : src.getStringList()) {
    builder.append(temp).append("  ");
}
Log.e("shawn", "src.stringList = " + builder.toString());
builder = new StringBuilder();
for (String temp : des.getStringList()) {
    builder.append(temp).append("  ");
}
Log.e("shawn", "des.stringList = " + builder.toString());

結果:

com.android.prototypepattern E/shawn: src.string = src   des.string = des
com.android.prototypepattern E/shawn: src.stringList = src 1  src 2  
com.android.prototypepattern E/shawn: des.stringList = src 1  src 2  des 1  des 2

上面的代碼很簡單,使用了 Cloneable 接口,但是其實 Cloneable 接口就是一個空類:

/**
 * This (empty) interface must be implemented by all classes that wish to
 * support cloning. The implementation of {@code clone()} in {@code Object}
 * checks if the object being cloned implements this interface and throws
 * {@code CloneNotSupportedException} if it does not.
 *
 * @see Object#clone
 * @see CloneNotSupportedException
 */
public interface Cloneable {
    // Marker interface
}

注釋很清楚了,是用來賦予一個類的 clone 功能的,繼續看看 Object 類的 clone 函數:

protected Object clone() throws CloneNotSupportedException {
    if (!(this instanceof Cloneable)) {
        throw new CloneNotSupportedException("Class " + getClass().getName() +
                                             " doesn't implement Cloneable");
    }

    return internalClone();
}

這也是為什麼需要繼承 Cloneable 接口的原因,不繼承該接口這裡就會直接拋出 CloneNotSupportedException 異常,internalClone 是一個 native 函數:

private native Object internalClone();

它直接操作內存中的二進制流,特別是復制大對象時,性能的差別非常明顯。
  這裡需要注意的是淺拷貝(影子拷貝)與深拷貝的問題,學過 C++ 對拷貝都應該印象很深,在上面的例子中用的是一個 String 和一個 ArrayList 的對象,對於這兩個對象,clone 函數裡的拷貝寫法是不一樣的,一個是直接 set ,另一個需要繼續調用 ArrayList 的 clone 方法生成一個 ArrayList 的拷貝對象 set 進 copy 對象中,如果直接將源對象中的 ArrayList 對象 set 進 copy 對象中,就會造成客戶端獲取到 copy 對象之後,可以通過 copy 對象修改源對象的數據,這在保護原型模式中是絕對不允許的,所以這裡不能使用淺拷貝,必須要使用深拷貝。Object 類的 clone 方法只會拷貝對象中的 8 種基本數據類型 ,byte 、char、short、int、long、float、double、boolean,對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝,如果要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝,如上面的 ArrayList 一樣。String 這個類型必須要單獨拿出來說一下,這個類型實際上算是一個淺拷貝,因為 src 與 拷貝後的 copy 對象指向的是同一個內存區域,但是由於 Java 中 String 的特殊不可變性(除去反射之外,不能修改一個 String 對象所指向的字符串內容),具體可以參考這篇博客:Java中的String為什麼是不可變的? – String源碼分析,所以從實際表現效果來看,String 和 8 種基礎類型一樣,可以認為是深拷貝。
  這裡寫圖片描述

示例與源碼

原型模式的代碼結構很簡單,我們這就以 Android 中的 Intent 類為例,來簡單了解一下 Intent 類的原型模式:
Intent.class

@Override
public Object clone() {
    return new Intent(this);
}
...
/**
 * Copy constructor.
 */
public Intent(Intent o) {
    this.mAction = o.mAction;
    this.mData = o.mData;
    this.mType = o.mType;
    this.mPackage = o.mPackage;
    this.mComponent = o.mComponent;
    this.mFlags = o.mFlags;
    this.mContentUserHint = o.mContentUserHint;
    if (o.mCategories != null) {
        this.mCategories = new ArraySet(o.mCategories);
    }
    if (o.mExtras != null) {
        this.mExtras = new Bundle(o.mExtras);
    }
    if (o.mSourceBounds != null) {
        this.mSourceBounds = new Rect(o.mSourceBounds);
    }
    if (o.mSelector != null) {
        this.mSelector = new Intent(o.mSelector);
    }
    if (o.mClipData != null) {
        this.mClipData = new ClipData(o.mClipData);
    }
}

和我們平時使用的 clone 函數不一樣,Intent 類並沒有調用 super.clone ,而是專門寫了一個拷貝構造函數用來克隆對象,我們在上文提到過,使用 clone 和 new 需要根據構造函數的成本來決定,如果對象的構造成本比較高或者構造較為麻煩,那麼使用 clone 函數效率較高,否則可以使用 new 的形式。這就和 C++ 中的拷貝構造函數完全一致,將原始對象作為構造函數的參數,然後在構造函數中獎原始對象的數據逐個拷貝一遍,這樣,整個克隆過程就完成了。

總結

原型模式是非常簡單的一個設計模式,它的核心問題就是對原始對象進行拷貝,在這個模式的使用過程中需要注意的一點就是:深、淺拷貝的問題。在開發過程中,為了減少錯誤,應該盡量使用深拷貝,避免操作副本時影響原始對象的問題。
  同時需要特別注意的是使用原型模式復制對象不會調用類的構造方法。因為對象的復制是通過調用 Object 類的 clone 方法來完成的,它直接在內存中復制數據,因此不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連訪問權限都對原型模式無效。
  原型模式的優點和缺點基本也就明了了:

優點原型模式是在內存中二進制流的拷貝,要比直接 new 一個對象性能好很多,特別是要在一個循環體內產生大量的對象時,原型模式可以更好的體現其優點,而且可以向客戶端隱藏制造新實例的復雜性;缺點直接在內存中拷貝,構造函數是不會執行的,在實際開發中應該注意這個潛在的問題,另外,對象的復制有時候相當復雜,特別需要注意的是不徹底深拷貝的問題。

 

創建型模式 Rules of thumb

有些時候創建型模式是可以重疊使用的,有一些抽象工廠模式和原型模式都可以使用的場景,這個時候使用任一設計模式都是合理的;在其他情況下,他們各自作為彼此的補充:抽象工廠模式可能會使用一些原型類來克隆並且返回產品對象。
  抽象工廠模式,建造者模式和原型模式都能使用單例模式來實現他們自己;抽象工廠模式經常也是通過工廠方法模式實現的,但是他們都能夠使用原型模式來實現;
  通常情況下,設計模式剛開始會使用工廠方法模式(結構清晰,更容易定制化,子類的數量爆炸),如果設計者發現需要更多的靈活性時,就會慢慢地發展為抽象工廠模式,原型模式或者建造者模式(結構更加復雜,使用靈活);
  原型模式並不一定需要繼承,但是它確實需要一個初始化的操作,工廠方法模式一定需要繼承,但是不一定需要初始化操作;
  使用裝飾者模式或者組合模式的情況通常也可以使用原型模式來獲得益處;
  單例模式中,只要將構造方法的訪問權限設置為 private 型,就可以實現單例。但是原型模式的 clone 方法直接無視構造方法的權限來生成新的對象,所以,單例模式與原型模式是沖突的,在使用時要特別注意。

源碼下載

https://github.com/zhaozepeng/Design-Patterns/tree/master/PrototypePattern

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved