設計模式與 Android 源碼

試圖看懂 --> 試圖模仿 --> 試圖記住 --> 試圖熟練

對于設計模式這是牛人們對代碼中的一定場景而進行提煉的結果, 對于一個進階的開發人員這是一個必不可少的技能. 當代碼越寫越好, 更易擴展更加靈活. 這對于Coder來說是最酷的事情.

通過 設計模式 和 Android源碼 中的關系, 可以更加清楚的記住各個模式的特點, 和源碼中的實現方式. 多練多分析之間的關系 這是必不可少的一步!

本篇就是 <Android源碼設計模式分析> 一書的縮減版. 這本書挺不錯的. 片中類圖講解出自 <大話設計模式>

  • 靈活之路 - 面向對象六大原則
  • 啟航之路 - UML類圖說明
  • 發現之路 - 23種設計模式

    • 單例模式 Singleton
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
    • 建造者模式 Builder
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
      • 實戰場景
    • 原型模式 Prototype
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
      • 實戰場景
    • 工廠方法模式 Factory
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
      • 實戰場景
    • 抽象工廠模式 Abstract Factory
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
    • 策略模式 Strategy
      • 模式介紹
      • 模式范例
      • Android源碼模式實現
      • 實戰場景

    靈活之路 - 面向對象六大原則

  • 單一職責原則SRP(Single Responsibility Principle)

    • 定義 : 就一個類而言, 應該僅有一個引起它的變化的原因. 通俗點 就是一個類應該是相關性很高數據封裝
    • 舉例 : 現在有一個圖片加載類. 但是這個類內部包含了 圖片下載的邏輯 , 圖片緩存的邏輯 這樣就使得這個類的職責過多, 隨著功能的不斷完善, 這個類的代碼和邏輯也變得縱橫交錯混合在了一起. 對于后續的修改維護擴展都是不利的. 所以讓兩個類組合起來, 一個類內部只負責 圖片下載 ,另一個類內部負責 圖片緩存 . 保持每個類的 單一職責
  • 開閉原則OCP(Open Close Principle)
    • 定義 : 軟件中的對象應該對于 擴展開放 的 但是對于 修改封閉 的. 通俗點 : 盡量通過 擴展的方式 來實現變化, 而不是通過修改已有的代碼來實現.
    • 舉例 : 此時我們實現了一個 雙緩存類單緩存類 . 在 圖片加載類 中進行這兩個緩存類的實例. 并對外暴露一個布爾值讓用戶設置是否使用雙緩存來決定內部緩存的邏輯. ok. 目前看可能沒有問題. 但是如果有一個更好的緩存算法類, 這時候每次都需要在 圖片加載類中修改代碼 . 這就違反了 OCP 原則, 利用 繼承,接口 的特性可以讓此類問題得以解決. 比如: 我們可以定義一個 緩存接口 , 在 加載類 中使用的個這個接口中的方法. 而這個接口的具體實現通過暴露一個方法讓外部調用的時候傳入, 以后如果有新的緩存類只需要調用方法傳入接口的子類就可以. 這樣就對于原始代碼修改進行了關閉, 而對于擴展是開放的.
  • 里氏替換原則LSP(Liskov Substitution Principle)
    • 定義 : 所有引用基類的地方必須能透明地使用其子類. 通俗點 :是基于繼承,多態兩大特性. 再簡單點 抽象
    • 舉例 : Window#show(View view) 這個方法接收一個 View , 但是我們可以 Button , TextView 等等. 其實很簡單. 我們常用只不過不知道這個名字而已. 所以 LSP 的原則的核心就是 抽象 . 抽象又依賴于繼承這個特性. 通常開閉原則和里氏替換是不離不棄的 例如上面 OCP 中舉得例子. 在外部調用就是利用了繼承的特性, 也就是 里氏替換
  • 依賴倒置原則DIP(Dependence Inversion Principle)
    • 定義 : 指代了一種特定的解耦形式, 使得高層次的模塊不依賴于低層次的模塊的實現細節的目的, 依賴模塊被顛倒了. 通俗點 : 在Java中依賴抽象(接口,抽象類), 而不依賴具體實現類. 模塊之間的依賴通過 抽象 發生, 實現類之間不發生直接的依賴關系, 其依賴關系是通過接口或抽象類產生.
    • 舉例 : 還是在 OCP 中的例子, 內部加載類依賴于也就是成員變量是 緩存接口 , 而不是具體的某一個 單緩存 或者 雙緩存 的實現類.
  • 接口隔離原則ISP(Interface Segregation Principles)
    • 定義 : 接口的依賴關系應該建立在最小的接口上. 通俗點 :接口隔離原則的目的是系統解開耦合, 從而容易重構, 更改和重新部署.
    • 舉例 : 在操作一些 IO文件,網絡 的時候我們總是伴隨著 try...catch...finally . 在最終調用塊中調用 close() 確保資源可以正確的釋放. 但這樣這樣的代碼不僅可讀性差可以每次都是寫一些冗余的模板代碼. 其實可以提供一個靜態方法, 而根據java中的的特性,之上操作的對象都會實現一個 標識接口Closeable ,這個接口標識了一個可關閉的對象有一個 close() . 所以這個靜態方法的形參接收一個 Closeable 接口,并在方法內調用 close() 即可. 仔細想想: 這個方法的形參在調用的時候傳入的實參是 里氏替換原則 , 而方法內部調用的是一個接口的 close() 方法,但傳入的可能是某一個實現類,那么這不就是 依賴導致原則 ,并且建立在最小化的依賴基礎上, 只要知道這個對象是可關閉的, 別的一概不關心, 這就是 接口隔離原則 .
  • 迪米特原則LOD(Law of Demeter)

    • 定義 : 一個對象應該對其他對象有 最少 的了解. 通俗點 : 一個類應該對自己需要耦合或調用的類知道的最少, 類的內部如果實現與調用者或者依賴者沒有關系, 調用者或者依賴者只需要知道他需要的方法即可, 其他一概不管.
    • 舉例 : 房間類, 中介類, 上班族類. 可以 上班族 應該只關心 中介類 , 而不需要關注 房間類 . 只需要 中介類 返回房子的地址即可. 而不需要通過調用 中介類 返回一個 房間類 . 這也就是代碼中需要注意的. 不要過度耦合, 要降低類之間的關系.

    啟航之路 - UML類圖說明

對于許多類組成的龐大關系網, 最好的辦法是通過圖來表示出其關系. 可以直觀的看出組合的元素, 元素直接是如何存在的, 元素與哪些元素直接存在著聯系等. 表示出來的圖就是 UML類圖 .

可以看如下一個稍微完整的一個 UML類圖

組成元素

  • 類和接口 : 通過黃色的矩形框來表示一個類, 例如上面鳥就是一個 普通類 , 如果類名是斜體那么就是 抽象類 , 如果和 飛翔 或者 唐老鴨 的表示法那么就是接口.
  • 訪問權限 : 通過 + 公共權限 , - 私有權限 , # 保護權限
  • 變量和方法 : 分別在第二行, 和第三行表示,抽象方法同樣斜體表示, 靜態屬性的用下劃線表示.

關系結構

  • 繼承關系 : 類與類之間的關系, 通過 空心三角+實線 表示, 通過 箭頭的方向指向父類 表述關系.
  • 實現關系 : 類與接口直接的關系, 通過 空心三角+虛線 表示, 通過 箭頭的方向指向接口 表述關系.
  • 關聯關系 : 當一個類知道另一個類的時候,可以使用 關聯 , 比如企鵝和氣候兩個類中, 企鵝類的變量有氣候類的引用 , 這個時候就如上圖之間的關系. 實線箭頭 表示, 箭頭指向被知道的類
  • 依賴關系 : 例如 動物 是依賴 氧氣和水的 , 就如 動物類中的方法形參類型依賴這兩個類型 . 如上圖動物和水之間關系. 使用 虛線箭頭 , 箭頭指向被依賴的類
  • 聚合關系 : 表示一種弱擁用, A可以包含B, 但B不可以包含A. 如大雁和雁群兩個類. 雁群類中會有一個數組,數組的元素是大雁類型. 這之間就是 聚合 . 使用 空心菱形+實線箭頭
  • 合成關系 : 也可以認為是 組合 . 是一種強擁有關系. 例如鳥類和翅膀類, 鳥類是整體, 翅膀類是部分. 并且其生命周期相同, 對應著就是 在鳥類初始化的時候,翅膀類也會隨之初始化 . 并且, 上圖中的鳥到翅膀還有 1..2 的字樣. 這稱為 基數 . 表明一段會有幾個實例, 例如一個鳥會有兩個翅膀. 如果一個類有無數個實例那就用 n 表示. 關聯關系 , 聚合關系 也是可以有 基數 的. 使用 實心菱形+實線箭頭 表示.

編程是門技術, 更加是一門藝術, 不能只滿足代碼結果運行正確就完事, 時常考慮如果讓代碼更加簡練, 更加容易維護, 更易擴展和復用, 這樣才可以真正提高.

發現之路 - 23種設計模式

單例模式 Singleton

模式介紹

  • 定義 : 確保某個類只有一個實例, 而且自行實例化并向整個系統提供這個實例.
  • 場景 : 確保一個類只會有一個對象實例, 避免產生多個對象消耗過多的資源, 或者某種類型的對象只應該有且只有一個. 如創建一個對象需要消耗的資源過多, 訪問IO和數據庫等資源時就可以考慮單例.

    模式范例

單例模式的實現有5種.

  • 餓漢式單例 --> 實現代碼
  • 懶漢式單例 -->實現代碼
  • 靜態內部類單例 -->實現代碼
  • 枚舉單例 -->實現代碼
  • 容器實現單例 -->實現代碼 這種方式在 android 源碼中存在.

知識擴展

枚舉實現法 最大的優點就是實現簡單, 但是在 android 卻比較消耗內存. 有一點與其他單例模式不同的是: 默認枚舉實例的創建 是線程安全的 . 為什么? 因為其他的單例在一種特定的場合下會重新創建對象,那就是 反序列化 .

反序列化 是從磁盤讀回數據并創建一個新的對象. 即使構造函數是私有的, 反序列化依然可以通過特殊的途徑去創建一個實例, 相當于調用了構造函數. 反序列化提供了一個很特別的 鉤子函數 , 類中具有一個私有的, 被實例化的方法 readResolver() , 這個方法可以讓開發人員控制對象的反序列化. 例如上面的幾個單例模式, 如果想杜絕單例對象在被反序列化時重新生成對象, 那么必須加入如下方法:

private Object readResolve() throws ObjectStreamException(){
    return sInstent;        // 返回單例中的實例對象
}

這樣在反序列化的時候就不是默認的重新生成一個新對象. 而對于枚舉,并不存在這個問題. 因為即使反序列化它也不會重新生成新的實例.

Android源碼對應模式

我們經常會在 Activity 中通過 getSystemService(String name) 這個函數來獲取系統的服務, 比如說 WMS , AMS , LayoutInflater 等等. 這些服務都會在某一時刻以 容器單例 的形式保存在應用中.

以 Adapter#getView() 中使用布局加載器 LayoutInflate.from(context).inflate(layoutId,null) 為例

會調用 ContextImpl#getSystemService(String) 方法獲取服務, 而方法內部只是從一個 SYSTEM_SERVICE_MAP 名字的集合中獲取了一個 ServiceFetcher 對象, 并從其中獲取具體的服務返回.

那么我們可以縷一下應用的啟動, 并定位到何時保存的這些服務到這個集合的.

  1. 首先應用的入口為 ActivityThread#main() ,在這個函數里面會創建 ActivityThread 對象, 并啟動消息循環(UI)線程, 調用 attach(boolean) 函數
  2. 在 attach(boolean) 中通過 Binder 機制與 ActivityManagerService 通信, 最終回調本類的 handlelaunchActivity() 函數.
  3. 然后執行 PerformLaunchActivity() 函數, 開始創建 Application , Context , Activity , 并把上下文關聯到 Activity 中, 最終調用 Activity#onCreate()

ok剛才大概流程是這樣的, 通過之前的分析我們知道, 各個系統服務是保存在 ContextImpl類中的 , 這個類是在上面的第3步中被初始化的. 看如下代碼, 就是服務被注冊的代碼, 時機也就是第一個 Context 被創建的時候.

class ContextImpl extends Context {
    // 存儲所有系統服務的集合
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =new HashMap<String, ServiceFetcher>();

    // 一個注冊服務的并添加到結合的方法
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
       if (!(fetcher instanceof StaticServiceFetcher)) {
           fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
       }
       SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }

    // 靜態語句塊, 只在類第一次被加載的時候調用, 保證了服務只被添加一次.
    static {
        // 注冊了LayoutInflate服務
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});

        registerService(INPUT_SERVICE, new StaticServiceFetcher() {
                public Object createStaticService() {
                    return InputManager.getInstance();
                }});

        /**
         *  后面省略一大坨的注冊的服務代碼
        **/
    }

}

建造者模式 Builder

模式介紹

一個復雜的對象有很多組成成分, 如汽車, 車輪, 方向盤, 發動機,等等. 為了在構建過程中對外部隱藏實現細節, 就可以使用 Builder 模式將部件和組裝過程分離, 使得構建過程和部件都可以自由擴展, 兩者之間的耦合也將到了最低.

  • 定義 : 將一個復雜對象的構建與它的表示分離, 使得同樣的構建過程可以創建不同的表示.
  • 場景 :

    1. 當初始化一個隊形特別復雜, 參數特別多, 且有很多參數都具有默認值時.
    2. 相同的方法, 不同的執行順序, 產生不同的事件結果時
    3. 多個部件或零件, 都可以裝配到一個對象中, 但是產生的運行結果又不相同.

    模式范例

    范例的UML類圖

    上例中通過具體 MacbookBuilder 類構建 Macbook 對象, 而 Director 封裝了構建復雜產品對象的過程, 對外隱藏了構建的細節. Builder 于 Director 一起將一個復雜對象的構建與它的表示分離, 是的同樣的構建過程可以創建不同的對象.

    可能你會覺得 唉? 怎么和我見過的Builder模式不一樣呢? ,這是因為 Director 這個角色經常會被忽略. 而直接使用一個 Builder 來進行對象的封裝, 并且這個 Builder 通常為 鏈式調用 , 它的每個 setter 方法都會返回 this 自身, 比如我們常用的 AlertDialog . 下節介紹.

Android源碼模式實現

在Android中最經典的 Builder 實現就是 AlertDialog . 看一下開發中的使用:

// 一個粗略的創建dialog
// 創建構建者builder角色
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setIcon(android.R.drawable.sym_def_app_icon)
      .setTitle("標題")
      .setMessage("message")
      // 設置點擊等..
      .setPositiveButton("確定", null);

// 構建
AlertDialog alertDialog = builder.create();

// 顯示
alertDialog.show();

從類名就可以看出這是一個 Builder模式 , 通過 Builder 對象來組裝 Dialog 的各個部分. 將 Dialog 的構造和表示進行了分離.

接下來看一下 AlertDialog 的源碼:

public class AlertDialog extends Dialog implements DialogInterface {
    // AlertController 這個對象會保存Builder對象中的各個參數
    private AlertController mAlert;

    // 實際上操作的是上面這個變量中的屬性
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }

    public void setMessage(CharSequence message) {
        mAlert.setMessage(message);
    }
    // 省略一坨代碼如各種setter等
    // Builder以內部類的形式存在
    public static class Builder {
        // 1.存儲AlertDialog的各個參數 如title,icon等
        private final AlertController.AlertParams P;

        // 構造函數
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, 0));
        }

        // 2. 設置參數, 我們構建的Builder設置的參數就是這些方法
        public Builder setTitle(int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

        // ....

        // 3.構建AlertDialog, 傳遞參數
        public AlertDialog create() {
            // 4.因為已經通過builder設置了參數, 接下來就可以創建真正需要的AlertDialog對象
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);

            // 5.將Builder類中的成員變量P應用到AlertDialog類中
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }      
    }   
}

對, 最后還調用了 AlertDialog#show() 函數, 這個函數主要做了如下幾件事情:

  1. 通過 dispatchOnCreate() 函數來調用 AlertDialog#onCreate() 函數
  2. 然后調用 AlertDialog#onStart() 函數
  3. 最后將 Dialog 的 DecorView 添加到 WindowManager 中.

    那么在看一下 onCreate() 函數的源碼及后續調用.

// AlertDialog類
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   mAlert.installContent();
}

// AlertController類
public void installContent() {
   // 設置窗口, 沒有title類型
   mWindow.requestFeature(Window.FEATURE_NO_TITLE);
   int contentView = selectContentView();
   // 設置窗口的內容視圖
   mWindow.setContentView(contentView);
   // 初始化AlertDialog其他子視圖的內容
   setupView();
   setupDecor();
}

這部分比較重要, 通過 Window#setContentView() 和Activity是一樣的過程, 設置了內容布局, 通過 AlertController 的構造函數可以發現加載布局資源就是 com.android.internal.R.layout.alert_dialog 這個文件, 之前的Builder中的各種 setter 方法就是把設置的內容傳入到這個布局當中.

可以看到 Android源碼中的AlertDialog 并沒有遵循 GOF設計模式 中經典的實現方式, 而是進行了變種, 但卻使其使用更加的方便. 這里 AlertDialog.Builder 這個類同時扮演了范例中的 builder , 具體實現builder , Director 的角色. 簡化了 Builder 設計模式, 因為模塊比較穩定不會存在變化, 根據具體場景簡化模式, 正是體現了靈活運用設計模式的實例.

實戰場景

就如 Picasso , Glide 等鏈式的調用, 你可以通過鏈式設置很多配置屬性, 也可以僅調用兩三此傳入必要參數即可. 是的調用實現更加靈活.

原型模式 Prototype

模式介紹

創建性模式, 從一個樣板對象中復制出一個內部屬性一致的對象, 其實就是 克隆 . 而被復制的對象就叫做 原型 , 多用于創建復雜的或者構造耗時的實例

  • 定義 : 用原型實例指定創建對象的種類, 并通過拷貝這些原型創建新的對象.
  • 場景 :
    1. 類初始化需要消耗非常多的資源, 這個資源包括數據,硬件資源等, 可通過原型拷貝避免這些消耗
    2. 通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限, 同樣可以使用原型模式
    3. 一個對象需要提供給其他對象訪問, 并且會能會對其修改屬性, 可以用原型拷貝多個對象提供使用

其實這個模式很簡單, 就是利用 Object#clone() 方法可以復制一份提供使用(clone是一個 native 方法). 但是需要注意, 通過實現 Cloneable 接口的原型模式在調用 clone 函數構造并不一定就比通過 new 方式的快, 只有當通過 new 構造對象較為耗時或者說成本較高時, 通過 clone 方法才能獲得效率提升.

UML類圖

模式范例

這里模式實現很簡單, 實現也比較少, 這里就貼出代碼

public class WordDocument implements Cloneable{

    // 文本
    public String mText;

    // 圖片名列表
    public ArrayList<String> mImages = new ArrayList<String>();

    public WordDocument(){
        System.out.println("-----------WordDocument構造函數-----------");
    }

    @Override
    protected WordDocument clone() {
        try {
            // 通過本地方法特殊途徑, 構建一個對象
            WordDocument doc = (WordDocument) super.clone();
            doc.mText = this.mText;

            // 因為Image是引用類型, 這樣直接賦值屬于淺拷貝, 再次對集合進行clone. 實現wordDocument的深拷貝
            doc.mImages = (ArrayList<String>) this.mImages.clone();
            return doc;
        }catch (Exception ex){}

        return null;
    }

    /**
     * 打印文檔內容
     */
    public void showDocument(){
        System.out.println("------------開始輸出內容---------------------");
        System.out.println("Text: "+mText);
        System.out.println("List: "+mImages.toString());
        System.out.println("------------輸出結束------------------------");
    }
}

與標準的原型模式相比 WordDocument 就是一個 具體實現的原型 對象. 而實現的 Cloneable 接口為 抽象的原型對象 .

其實 Cloneable 這個接口內部沒有任何方法, 所以其本質就是 標識接口 ,只是表明這個類的對象是 可拷貝的 , 而 clone() 這個方法是 Objec 類中的, 如果沒有標識這個接口, 那么調用會拋出異常.

深拷貝淺拷貝

例如上面的代碼中進行修改一下

@Override
protected WordDocument clone() {
   try {
       // 通過本地方法特殊途徑, 構建一個對象
       WordDocument doc = (WordDocument) super.clone();
       doc.mText = this.mText;

       // 這里進行修改 那么此時屬于淺拷貝
       doc.mImages = this.mImages;
       return doc;
}

你可能應該發現了什么, 其實本質不過就是通過 super.clone() 構建了一個本類對象的初始狀態, 然后把被拷貝的對象的各個屬性值進行 賦值 操作而已.

的確, 就是如此. 就如上面兩處不同的代碼,

  • 淺拷貝 : 也稱 影子拷貝 , 拷貝出來的對象并不是完全一份獨立的對象, 新的對象某些屬性如 引用傳遞 可能會 引用 原始對象的對應屬性值, 也就是說, 對淺拷貝的屬性可能會影響到原始數據的屬性.
  • 深拷貝 : 拷貝出一份原始對象, 并對原始對象的屬性值, 進行 復制添加 到新拷貝的對象的各個屬性上. 這樣拷貝出來的對象與原始對象不存在任何關聯, 只作為一個數據的副本存在.

上面因為 mImages 的類型是 ArrayList 如果直接進行賦值那么屬于引用傳遞, 共享的一份數據源, 而如果在對 ArrayList 進行一次 clone , 那么相當于又構建了一個集合并進行數據的復制.

而 mText 雖然是對象, 但是因為是 String 類型, 屬于安全類型, 由于final類,實例不可更改的特性. 如果對副本進行字符串的修改, 只不過是把原引用刪除,重新指向了新的字符串.

Android源碼對應實現

上面我們說了通過對集合再次調用 clone() 即可完成深拷貝. 那么看一下 ArrayList 源碼

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

    transient Object[] elementData; 
    private int size;

    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

ArrayList 的內部克隆實現很簡單, 我們都知道 ArrayList 內部是通過數組的數據結構來實現的. 通過 Arrays 工具類對原始集合的數據進行賦值并添加到一個新的數組并返回, 而返回的數組掛到了克隆出來對象上的 elementData 變量上.

而集合的大小 size 沒有被進行賦值? 因為其類型是整型, 屬于 值傳遞 , 在clone之后原始值通過值傳遞到了新對象中, 即使修改也不會對原始對象有任何的影響.

那么 Android 源碼中的實現是什么?

Intent , 我們看如下代碼

Intent intent = new Intent("某一個activity的action");
intent.putExtra("result", "Successful");

// 調用克隆方法
Intent clone = (Intent) intent.clone();
startActivity(clone);

這樣同樣沒問題, 一樣的效果. 那么看一下 Intent#clone() 內部是如何實現的.

@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<String>(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);
   }
}

很簡單不需要解釋了, 手動 new 的并進行數據復制. 相當于封裝了一下復制的細節而已.

但是為什么沒有調用 super.clone() 來實現拷貝呢? 之前說過使用 clone 還是 new 關鍵字是需要根據構造對象的成本來決定的, 如果對象的構造成本比較復雜或者麻煩, 那么 clone 則是一種更優的選擇, 否則就可以使用 new 的形式. 這和 c++ 拷貝構造函數是一樣的.

實戰場景

當登錄模塊登錄成功之后, 會把一些個人信息,token等信息在保存類中的某個數據結構上, 并通過一個方法對外暴露出去, 提供其他模塊使用. 但是如果你返回的是一個數據結構也就是一個對象, 這個對象包含了很多個人信息, 但是正常來說, 對于外部應該只提供查看數據的能力, 不應該提供修改的能力.

所以這個使用, 就可以對登錄模塊對外暴露的方法進行修改, 利用 原型模式 對外返回的是一個內部數據的 深拷貝 , 這樣就把可能出現的隱患徹底的隔絕了.

說明

原型模式 是通過內存中二進制流的方式拷貝, 要比直接通過 new 一個對象性能更好, 特別是循環體內產生大量對象是. 但是注意, 因為是 二進制流的拷貝 , 所以構造函數是不會執行的. 這點要明確記牢.

工廠方法模式 Factory

模式介紹

創建型設計模式, 其實這個模式可能在開發中出現很多回了, 只是并不了解什么是工廠模式的概念.

  • 定義 : 定義一個用于創建的對象的接口, 讓子類決定實例化哪個類
  • 場景 : 在任何需要生成復雜對象的地方, 都可以使用工廠方法模式. 復雜對象適合使用工廠模式, 用 new 就可以完成創建的對象無需使用工廠模式.

工廠方法模式完全符合設計模式原則, 降低了對象之間的耦合度, 而且, 工廠方法模式依賴于抽象的架構, 將實例化的任務交由了子類實現.

模式范例

這是范例的UML類圖.

其實這里, 可以去掉抽象的工廠類, 只需要一個工廠即可. 這樣會更加簡潔直觀.

Android源碼對應實現

List 和 Set 不陌生, 都繼承 Collection 接口, 而 Collection 接口繼承 Iterable 接口, 而這個接口很簡單就一個 iterator() 方法, 如下

public interface Collection<E> extends Iterable<E> {
    // ....
}

public interface Iterable<T> {
    Iterator<T> iterator();

    // 可能JDK1.8之后添加兩個默認方法, 這里我們不需要關心
}

關于 List 和 Set 迭代器的方法遍歷元素應該都用過. 那么看一下源碼實現.

public class ArrayList<E> extends AbstractList<E> implements Cloneable, Serializable, RandomAccess {

@Override public Iterator<E> iterator() {
        return new ArrayListIterator();
    }

    private class ArrayListIterator implements Iterator<E> {
        private int remaining = size;

        private int removalIndex = -1;

        private int expectedModCount = modCount;

        public boolean hasNext() {
            return remaining != 0;
        }

        @SuppressWarnings("unchecked") public E next() {
            ArrayList<E> ourList = ArrayList.this;
            // 返回集合大小元素, 還有幾個未遍歷
            int rem = remaining;
            if (ourList.modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (rem == 0) {
                throw new NoSuchElementException();
            }
            remaining = rem - 1;
            return (E) ourList.array[removalIndex = ourList.size - rem];
        }

        public void remove() {
            Object[] a = array;
            int removalIdx = removalIndex;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            if (removalIdx < 0) {
                throw new IllegalStateException();
            }
            System.arraycopy(a, removalIdx + 1, a, removalIdx, remaining);
            a[--size] = null;  // Prevent memory leak
            removalIndex = -1;
            expectedModCount = ++modCount;
        }
    }

}
// hashSet 復寫邏輯
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable,Serializable {
    public Iterator<E> iterator() {
        return backingMap.keySet().iterator();
    }
}
// HashMap 復寫邏輯
public class HashMap<K, V> extends AbstractMap<K, V> implements Cloneable, Serializable {
    Iterator<K> newKeyIterator() { return new KeyIterator();   }

    private final class KeyIterator extends HashIterator
            implements Iterator<K> {
        public K next() { return nextEntry().key; }
    }

}
  • HashSet 的 iterator 方法會返回成員變量 backingMap 中對應 HashSet 對象元素的迭代器對象, 最終返回的是 KeySet 中的一個迭代器對象
  • ArrayList 和 HashMap 中的 iterator() 就相當一個工廠方法, 專為new對象而生 !

而 Android 中, 看一下如下代碼

public class MainActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new FrameLayout(this));   
    }
}

通過 onCreate() 這個方法, 我們可以構建出任何樣式的根布局, 如 LinearLayout , TextView 等等. 我們在不同的 Activity#onCreate() 方法將設置的布局通過 setContentView() 函數傳遞給 frameworks 并顯示出來. 這不就是一個工廠模式的結構. 方法內部可以創建不同的對象, 產生不同的實例.

實戰場景

例如對數據的持久化, 可以通過的途徑有 SP , File , SQLite 等. 但是對數據的操作無非就是 增刪改查 , 那么我們可以抽象一個抽象類并定義CURD抽象方法. SP , File , SQLite 分別繼承抽象類, 并在抽象方法實現自己的處理邏輯. 然后就可以創建一個 工廠類 , 工廠類有一個方法, 形參為產品實現類的字節碼 , 返回一個 泛型上限限定是產品的抽象類 對象, 方法內部通過字節碼反射具體的產品類實例.

這樣在使用的使用, 我們只需有通過 工廠方法 傳入的不同 產品Class 就可以構建不同的實例, 而數據的CRUD通過 依賴倒置 抽象特性, 高層不需要依賴底層的類.

抽象工廠模式 Abstract Factory

模式介紹

創建型設計模式, 之前工廠模式會生產某一個產品, 但是如果說, 不同的操作系統圖形的場景下的兩個產品 按鈕文本框 . 對于每一個操作系統, 其本身就構成了一個單獨的產品. 兩種產品兩種變化, 這種情況就較之前的普通工廠升級了復雜度, 如: Android 中的 Button 和 TextView , iOS 中的 Button 和 TextView 或者 WindowPhone 場景...

  • 定義 : 為創建一組相關或者是相互依賴的的對象提供一個接口, 而不需要指定他們的具體類
  • 場景 : 一個對象族有相同的約束時可以使用 抽象工廠 , 如 android 和 iOS 都有打電話軟件和短信軟件, 兩者都屬于 軟件的范疇 , 但是他們的操作平臺不同, 實現邏輯也不會相同. 這個時候就可以使用 抽象工廠方法模式

    模式范例

范例UML圖

看一下運行結果:

如果這是時候, 如果想創建一種使用 普通輪胎 , 新款發動機 的車型. 只需要繼承抽象工廠, 并使用原有的普通輪胎類, 并繼承 IEngfine 實現一款新的發動機類. 即可完成擴展. 這就是通過接口擴展.

上面的范例, 對于每一個造車的工廠, 內部使用的零件不管哪個車場都是具有抽象的輪胎和發送機類. 這樣可以達到一種自由組合的狀態.

但是弊端也顯示出來了, 不僅需要擴展新的 工廠類 還要擴展 新的組件類 .

Android源碼對應實現

抽象工廠在 Android 實現較少, 上一節說 onCreate() 方法就相當于一個工廠方法. 那么對于另外一個組件 Service#onBind() 同樣也可以看做一個工廠方法.

如果從 frameworks 層的角度來看 Activity 和 Service 可以看做一個具體的工廠, 這樣來看相當于一個抽象方法模式的雛形也沒錯.

另一個更像的例子是 Android 底層對 MediaPlayer 使用. 這里書上噼里啪啦一堆C語言. 我就不抄了....

策略模式 Strategy

模式介紹

開發中可能有這樣的情況: 實現某一個功能可以有多中算法或者策略, 我們根據不同的功能來選擇不同的算法. 針對這種情況, 1.可以在一個類中封裝多個方法, 每個方法實現不同算法. 2.通過 if..else if..else.. 條件判斷來決定使用哪種算法. 但是這兩種都是 硬編碼 實現. 并且隨著算法的增多類也就變得臃腫, 維護的成本隨之變高. 如果需要增加一種新的算法, 必然需要對算法類進行修改. 這就違反了 OCP 原則和 單一職責 的原則.

  • 定義 : 策略模式定義了一系列的算法, 并將每一個算法封裝起來, 而且使它們還可以相互替換. 策略模式讓算法獨立于使用它的客戶而獨立變化.
  • 場景 :

    • 針對同一類型問題的多種處理方式, 僅僅是具體行為有差別時
    • 需要安全地封裝多種同一類型的操作時
    • 出現同一抽象類有多個子類, 而又不需要使用 if-else 或者 switch 等來選擇具體子類.

    模式范例

最方便的記憶法就是記住, 策略模式 可以去掉 if-else 或者 switch . 語句, 即使后續會擴展通過接口來進行擴展, 不會對源代碼進行修改. 滿足了 OCP開閉原則 .

看一下范例代碼: --> 對于交通費用的計算, 計算的算法可能會有公交, 地鐵等...

范例類圖:

在看一下代碼的使用以及結果-->

public static void main (String arg[]){
   // 創建操作策略的環境類
   TranficCalculator calculator = new TranficCalculator();
   // 設置公交車的策略, 并準備計算
   calculator.setStrategy(new BusStrategy());
   System.out.println("公交車-->計算9公里價格: "+calculator.calculatePrice(9));

   // 設置地鐵的策略, 并準備計算
   calculator.setStrategy(new SubwayStrategy());
   System.out.println("地鐵-->計算9公里價格: "+calculator.calculatePrice(9));
}

// 結果-->
公交車-->計算9公里價格: 1
地鐵-->計算9公里價格: 4

你應該可以發現, 這種方式在隱藏實現的同時, 可擴展性 變得很強, 如果此時需要增加一個出租車的計算策略, 那么只需要添加一個實現了 計算策略接口 即可. 對原始代碼的修改進行了 關閉 , 并對擴展 開放 .

Android源碼對應實現

動畫里面的 插值器Interpolator 利用了策略模式, 利用 Interpolator 策略的抽象, LinearInterpolator , CycleInterpolator 等插值器為具體的實現策略, 通過注入不同的插值器實現不同的動態效果.

看一下大概的類圖

  • 動畫中的 TimeInterpolator 時間插值器, 它的作用是根據時間流逝的百分比計算出當前屬性值改變的百分比, 內置的插值器有如下幾種
    • 線性插值器(LinearInterpolator) 用于勻速動畫
    • 加速減速插值器(AccelerateDecelerateInterpolator) :起始時動畫加速, 結尾時動畫減速
    • 減速插值器(DecelerateInterpolator) : 用于隨著時間的推移動畫越來越慢.
  • 動畫中的 TypeEvalutor 類型估值器: 根據當前屬性改變的百分比來計算改變后的屬性值. 內置的類型估值器有如下幾種
    • 整型估值器(IntEvalutor)
    • 浮點型估值器(FloatEvalutor)
    • Color估值器(ArgbEvalutor)

接下來就開始回憶一下從一個動畫開始后, 代碼究竟做了什么?

對于源碼的起始點入口就是調用 View的startAnimation()

public void startAnimation(Animation animation) {
    // 1.初始化動畫的開始時間
   animation.setStartTime(Animation.START_ON_FIRST_FRAME);
   // 2.對View設置動畫
   setAnimation(animation);
   // 3.刷新父類緩存
   invalidateParentCaches();
   // 4.刷新View本身及子View
   invalidate(true);
}

這里首先設置了動畫的起始時間, 然后將該動畫設置到 View 中, 最后再向 ViewGroup 請求刷新視圖, 隨后 ViewGroup 會調用 dispatchDraw() 方法對這個 View 所在的區域進行重繪. 其實對于某一個 View 的重繪最終是調用其 ViewGroup 的 drawChild(...) 方法. 跟入一下

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
   // 簡單的轉發
   return child.draw(canvas, this, drawingTime);
}

boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    // ....
    // 查看是否需要清除動畫信息
    final int flags = parent.mGroupFlags;
    // 省略無關代碼

    // 獲取設置的動畫信息
    final Animation a = getAnimation();

    if (a != null) {
            // 繪制動畫
           more = drawAnimation(parent, drawingTime, a, scalingRequired);
           //...
       } 
}

父類會調用子類的 draw 方法, 其中會先判斷是否設置了清除動畫的標記, 然后再獲取該 View 動畫信息, 如果設置了動畫, 就會調用 View#drawAnimation() 方法.

private boolean drawAnimation(ViewGroup parent, long drawingTime,
       Animation a, boolean scalingRequired) {
   Transformation invalidationTransform;
   final int flags = parent.mGroupFlags;
   final boolean initialized = a.isInitialized();
   // 1. 判斷動畫是否已經初始化過
   if (!initialized) {
       a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
       a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
       if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
       // 如果設置了動畫的監聽, 則觸發對應的回調
       onAnimationStart();
   }
   // 獲取Transformation對象, 存儲動畫的信息
   final Transformation t = parent.getChildTransformation();
   // 2. 調用Animation#getTransformation, 通過計算獲取動畫的相關值
   boolean more = a.getTransformation(drawingTime, t, 1f);


   if (more) {
        // 3. 根據具體實現, 判斷當前動畫類型是否需要進行調整位置大小, 然后刷新不同的區域
       if (!a.willChangeBounds()) {
           // ...
       } else {
           // 獲取重繪區域
           a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
                   invalidationTransform);
           parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;

            // 更新計算有效區域
           final int left = mLeft + (int) region.left;
           final int top = mTop + (int) region.top;

           // 進行區域更新
           parent.invalidate(left, top, left + (int) (region.width() + .5f),
                   top + (int) (region.height() + .5f));
       }
   }
   return more;
}

drawAnimation 中主要操作是動畫的初始化, 動畫操作, 界面刷新. 動畫的回調監聽 onStart() 會在動畫進行初始化的時候調用, 動畫的具體實現是通過 Animation#getTransformation() 方法.這個方法主要獲取了 縮放系數 和調用 Animation.getTransformation(long, Transformation) 來計算和應用動畫效果.

public boolean getTransformation(long currentTime, Transformation outTransformation) {
   //...
   float normalizedTime;
   // 1.計算當前時間的流逝百分比
   if (duration != 0) {
       normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
               (float) duration;
   } else {
       // time is a step-change with a zero duration
       normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
   }
   // 動畫是否完成標記
   final boolean expired = normalizedTime >= 1.0f;
   mMore = !expired;

   if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
        // 2.通過插值器獲取動畫執行百分比  , 這里獲取的方法就是通過策略模式
       final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
       // 3.應用動畫效果
       applyTransformation(interpolatedTime, outTransformation);
   }

   // 4. 如果動畫執行完畢, 那么觸發動畫完成的回調或者執行重復動畫等操作
   // ...
   if (!mMore && mOneMoreTime) {
       mOneMoreTime = false;
       return true;
   }
   return mMore;
}

這段代碼, 先計算已經流逝的的時間百分比, 然后再通過 具體的插值器 重新計算這個百分比, 也就是上面的第二步. 而具體是哪一個插值器是通過之前說的策略模式來實現的.

第3步調用了 applyTransformation , 這個方法在基類 Animation 中是空實現, 可以在子類查看實現如 ScaleAnimation , AlphaAnimation 等查看. 當這個方法內部主要通過 矩陣 來實現動畫. 當這個方法執行完畢之后, View的屬性也就發生了變化, 不斷地重復這個過程, 動畫就隨之產生.

實戰場景

當我們自己組裝了一個隊列請求, 對于這個隊列的處理方式默認可能是 先處理進入隊列的 , 但是如果想實現一個可以 先處理后入隊的 , 和 隨機讀取隊列元素 . 那么為了以后的擴展不影響源代碼, 那么可以通過 策略模式 在代碼中通過對 策略抽象 面向接口,抽象編程. 是具體的實現有后續的傳入的子類來決定.

 

 

來自:https://github.com/suzeyu1992/repo/tree/master/project/design-pattern/瞰-設計模式與Android(篇一)

 

 本文由用戶 cailangwei 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!