編輯:關於Android編程
這篇博客我們來介紹一下剩下的最後一個創建型模式:原型模式(Prototype Pattern)。該模式有一個樣板實例,用戶從這個樣板對象中復制出一個內部屬性一致的對象,這個過程在 C++ 中就是一個克隆。被復制的實例就是我們所稱的“原型”,這個原型是可定制的。原型模式多用於創建復雜的或者構造耗時的實例,因為這種情況下,復制一個已經存在的實例可以使程序運行效率更高。
這個模式的重點在於,客戶端的代碼在不知道要實例化何種特定類的情況下,就可以制造出新的實例。
用原型實例指向創建對象的種類,並通過拷貝這些原型創建新的對象。
原型模式的適用場景:
我們來看看原型模式的 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
我們直接使用系統的 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 方法來完成的,它直接在內存中復制數據,因此不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連訪問權限都對原型模式無效。
原型模式的優點和缺點基本也就明了了:
有些時候創建型模式是可以重疊使用的,有一些抽象工廠模式和原型模式都可以使用的場景,這個時候使用任一設計模式都是合理的;在其他情況下,他們各自作為彼此的補充:抽象工廠模式可能會使用一些原型類來克隆並且返回產品對象。
抽象工廠模式,建造者模式和原型模式都能使用單例模式來實現他們自己;抽象工廠模式經常也是通過工廠方法模式實現的,但是他們都能夠使用原型模式來實現;
通常情況下,設計模式剛開始會使用工廠方法模式(結構清晰,更容易定制化,子類的數量爆炸),如果設計者發現需要更多的靈活性時,就會慢慢地發展為抽象工廠模式,原型模式或者建造者模式(結構更加復雜,使用靈活);
原型模式並不一定需要繼承,但是它確實需要一個初始化的操作,工廠方法模式一定需要繼承,但是不一定需要初始化操作;
使用裝飾者模式或者組合模式的情況通常也可以使用原型模式來獲得益處;
單例模式中,只要將構造方法的訪問權限設置為 private 型,就可以實現單例。但是原型模式的 clone 方法直接無視構造方法的權限來生成新的對象,所以,單例模式與原型模式是沖突的,在使用時要特別注意。
https://github.com/zhaozepeng/Design-Patterns/tree/master/PrototypePattern
Android對於圖片處理,最常使用到的數據結構是位圖——Bitmap,它包含了一張圖片所有的數據。整個圖片都是由點陣和顏色值組成的,所謂點陣就是
Fragment簡介Fragment 表示 Activity 中的行為或用戶界面部分。您可以將多個Fragment組合在一個 Activity 中來構建多窗格 UI,以及
--------------------------------------廣播機制簡介-----------------------------------------
序言:Activity作為Android四大組件之一,其重要性不言而喻。本文目錄結構:1.Acticity是什麼2.Activity的創建3.Activity的生命周期4