android中常見的設計模式有哪些?

来源:http://www.cnblogs.com/sjm19910902/archive/2017/02/10/6386542.html
-Advertisement-
Play Games

建造者模式 建造者模式最明顯的標誌就是Build類,而在Android中最常用的就是Dialog的構建,Notification的構建也是標準的建造者模式。 建造者模式很好理解,如果一個類的構造需要很多參數,而且這些參數並不都是必須的,那麼這種情況下就比較適合Builder。 比如構建一個Alert ...


建造者模式

建造者模式最明顯的標誌就是Build類,而在Android中最常用的就是Dialog的構建,Notification的構建也是標準的建造者模式。

建造者模式很好理解,如果一個類的構造需要很多參數,而且這些參數並不都是必須的,那麼這種情況下就比較適合Builder。

比如構建一個AlertDialog,標題、內容、取消按鈕、確定按鈕、中立按鈕,你可能只需要單獨設置幾個屬性即可;另外在我的OkHttpPlus項目中,構造一個Http請求也是這樣的,有可能你只需要設置URL,有可能需要添加請求參數、Http Header等,這個時候建造者模式也是比較合適的。

單例模式

單例在Android開發中經常用到,但是表現形式可能不太一樣。

以ActivityManager等系統服務來說,是通過靜態代碼塊的形式實現單例,在首次載入類文件時,生成單例對象,然後保存在Cache中,之後的使用都是直接從Cache中獲取。

class ContextImpl extends Context {

    static {
        registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                public Object createService(ContextImpl ctx) {
                    return new ActivityManager(ctx.getOuterContext(),       ctx.mMainThread.getHandler());
                }});
    }
}

當然,還有更加明顯的例子,比如AccessibilityManager內部自己也保證了單例,使用getInstance獲取單例對象。

 public static AccessibilityManager getInstance(Context context) {
        synchronized (sInstanceSync) {
            if (sInstance == null) {

               ......

                IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
                IAccessibilityManager service = iBinder == null
                        ? null : IAccessibilityManager.Stub.asInterface(iBinder);
                sInstance = new AccessibilityManager(context, service, userId);
            }
        }
        return sInstance;
    }

除此之外,還有一些偽單例,比如Application,預設情況下在一個進程中只存在一個實例,但是Application不能算是單例,因為它的構造方法未私有,你可以生成多個Application實例,但是沒有用,你沒有通過attach()綁定相關信息,沒有上下文環境。

public Application() {
        super(null);
    }

單例的使用場景也很簡單,就是一個App只需要存在一個類實例的情況,或者是類的初始化操作比較耗費資源的情況。在很多開源框架中,我們只需要一個對象即可完成工作,比如各種網路框架和圖片載入庫。

除此之外,因為單例的實現方式很多,比如懶漢式、餓漢式、靜態內部類、雙重鎖檢查、枚舉等方式,所以要清楚每種實現方式的主要特點和使用場景。

原型模式

原型模式在開發中使用的並不多,但是在源碼中卻有所體現。

書中以Intent介紹了原型模式,是通過實現Cloneable介面來做的

public class Intent implements Parcelable, Cloneable {
    @Override
        public Object clone() {
         return new Intent(this);
        }
    }

其實這樣來看的話,原型模式也比較好理解,就是你想更快的獲取到一個相同屬性的對象,那麼就可以使用原型模式,比如這裡就獲取到了一個Intent對象,Intent裡面的屬性與被clone的相同,但是兩者並無關聯,可以單獨使用。

除了實現Cloneable介面,你完全可以自己定義一個方法,來獲取一個對象。我這裡以PhoneLayoutInflater為例子介紹。

PhoneLayoutInflater是LayoutInflater的子類,如果我們在Activity中獲取LayoutInflate的話,是通過下麵方法

 @Override public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

可以看到,如果為null,就會調用cloneInContext(),這個方法在LayoutInflate是抽象方法,具體實現在PhoneLayoutInflater中

  public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }

可以看到,這也是一個原型模式,所以我們不要太糾結於形式,更重要的是理解這樣做的好處。

除了在源碼中可以看到原型模式,在開源框架中也可以看到,比如OkHttpClient中就存在著下麵的方法

/** Returns a shallow copy of this OkHttpClient. */
  @Override public OkHttpClient clone() {
    return new OkHttpClient(this);
  }

可以看到,實現和前面的完全相同,也是new了一個對象返回,因為OkHttpClient的構造過程比較複雜,參數眾多,所以用這種方式來直接生成新對象,成本很低,而且能保留之前對象的參數設置。

工廠方法模式

書中對於工廠方法模式的一個觀點很新奇,就是Activity.onCreate()可以看做是工廠方法模式,來生成不同的View對象填充界面。

但是我對這個說法不太苟同,原因有兩點:一是這種形式不太符合工廠方法,沒有抽象,沒有實現,不符合一般格式,也不是靜態方法,不可看做是靜態工廠方法;二是沒有以生成對象為結果,即不是return view來生成對象,只是通過setContentView()來設置了屬性而已。這就像是給一個Activity設置了背景顏色一樣。當然,設計模式這東西一個人有一個人的看法。

靜態工廠方法在Android中比較明顯的例子應該就是BitmapFactory了,通過各種decodeXXX()就可以從不同渠道獲得Bitmap對象,這裡不再贅述。

策略模式

在書中策略模式講得非常好,結合動畫的插值器用法,我們可以很好的理解策略模式的形式和用法。

在我看來,策略模式就相當於一個影碟機,你往裡面插什麼碟子,就能放出什麼電影。

同樣,在OkHttpPlus的封裝中,為了對網路返回值進行解析,我使用了策略模式。當然我寫代碼的時候還不知道策略模式,是寫完了之後突然想到,這就是策略模式啊!

策略模式的精髓就在於,你傳入一個類,後面的處理就能按照這個類的實現去做。以動畫為例,設置不同的插值器對象,就可以得到不同的變化曲線;以返回值解析為例,傳入什麼樣的解析器,就可以把二進位數據轉換成什麼格式的數據,比如String、Json、XML。

責任鏈模式

書中對於責任鏈模式選取的例子非常有代表性,那就是Android的觸摸機制,這個看法讓我從另一個維度去理解Android中的觸摸事件傳遞。

我在這裡提到這個模式,並不想說太多,只是簡單的推薦你讀一下這一章的內容,相信你也會有收穫的。

觀察者模式

Android中的觀察者模式應該是用的非常頻繁的一種模式了,對於這個模式的使用場景就一句話:你想在某個對象發生變化時,立刻收到通知。

書中介紹觀察者模式使用的是ListView的Adapter為例子,我之前知道Adapter屬於適配器模式,不知道這裡還有觀察者模式的身影,學到了。

Android裡面的各種監聽器,也都屬於觀察者模式,比如觸摸、點擊、按鍵等,ContentProvider和廣播接收者也有觀察者模式的身影,可以說是無處不在。

除此之外,現在很多基於觀察者模式的第三方框架也是非常多,比如EventBus、RxJava等等,都是對觀察者模式的深入使用,感興趣的同學可以研究一下。

模板方法模式

這個模式我之前見的比較少,但是理解之後,就會發現這個模式很簡單。

我覺得,模板方法模式的使用場景也是一句話:流程確定,具體實現細節由子類完成。

這裡要關註一下『流程』這個關鍵字,隨便拿一個抽象類,都符合"具體實現細節由子類完成"的要求,關鍵就在於是否有流程,有了流程,就叫模板方法模式,沒有流程,就是抽象類的實現。

書中講這個模式用的是AsyncTask,各個方法之間的執行符合流程,具體實現由我們完成,非常經典。

另外一個方面,Activity的生命周期方法可以看做是模板方法模式,各個生命周期方法都是有順序的,具體實現我們可以重寫,是不是和前面的要求很符合?關於這方面的理解,可以參考我的這篇文章:對Activity生命周期的理解

除了Android裡面的模板方法模式,在其他開源項目中也存在著這個模式的運用。比如鴻洋的OkHttp-Utils項目,就是模板方法模式的典型實現。將一個Http請求的過程分割成幾部分,比如獲取URL,獲取請求頭,拼接請求信息等步驟,這幾個步驟之前有先後順序,就可以這樣來做。

代理模式和裝飾器模式

之所以把這兩個放在一起說,是因為這兩種模式很像,所以這裡簡單介紹下他們之間的區別,主要有兩點。

  1. 裝飾器模式關註於在一個對象上動態的添加方法,而代理模式關註於控制對對象的訪問
  2. 代理模式,代理類可以對它的客戶隱藏一個對象的具體信息。因此,當使用代理模式的時候,我們常常在一個代理類中創建一個對象的實例。而當我們使用裝飾器模式的時候,通常的做法是將原始對象作為一個參數傳給裝飾者的構造器

這兩句話可能不太好理解,沒關係,下麵看個例子。

代理模式會持有被代理對象的實例,而這個實例一般是作為成員變數直接存在於代理類中的,即不需要額外的賦值。

比如說WindowManagerImpl就是一個代理類,雖然名字上看著不像,但是它代理的是WindowManagerGlobal對象。從下麵的代碼中就可以看出來。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    ......

    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

    @Override
    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        mGlobal.updateViewLayout(view, params);
    }

    @Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }
}

從上面的代碼中可以看出,大部分WindowManagerImpl的方法都是通過WindowManagerGlobal實現的,而WindowManagerGlobal對象不需要額外的賦值,就存在於WindowManagerImpl中。另外,WindowManagerGlobal中其實有大量的方法,但是通過WindowManagerImpl代理之後,都沒有暴露出來,對開發者是透明的。

我們再來看一下裝飾器模式。裝飾器模式的目的不在於控制訪問,而是擴展功能,相比於繼承基類來擴展功能,使用裝飾器模式更加的靈活。

書中是以Context和它的包裝類ContextWrapper講解的,也非常的典型,我這裡就不在贅述了,貼出一些代碼來說明裝飾器模式的形式。

public class ContextWrapper extends Context {
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
}

但是還有一個問題,就是在ContextWrapper中,所有方法的實現都是通過mBase來實現的,形式上是對上號了,說好的擴展功能呢?

功能擴展其實是在ContextWrapper的子類ContextThemeWrapper裡面。

在ContextWrapper裡面,獲取系統服務是直接通過mBase完成的

@Override
    public Object getSystemService(String name) {
        return mBase.getSystemService(name);
    }

但是在ContextThemeWrapper裡面,對這個方法進行了重寫,完成了功能擴展

@Override public Object getSystemService(String name) {
        if (LAYOUT_INFLATER_SERVICE.equals(name)) {
            if (mInflater == null) {
                mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
            }
            return mInflater;
        }
        return getBaseContext().getSystemService(name);
    }

當然,如果不存在功能擴展就不算是裝飾器模式了嗎?其實設計模式本來就是『仁者見仁,智者見智』的事情,只要你能理解這個意思就好。

外觀模式

外觀模式可能看到的比較少,但是其實不經意間你就用到了。

這裡以我的一個開源項目KLog來說吧,在最開始寫這個類的時候,就只有KLog這一個類,完成基本的Log列印功能,後來又添加了JSON解析、XML解析、Log信息存儲等功能,這個時候一個類就不太合適了,於是我把JSON、XML、FILE操作相關的代碼抽取到單獨的類中,比如JSON列印的代碼

public class JsonLog {

    public static void printJson(String tag, String msg, String headString) {

        String message;

        try {
            if (msg.startsWith("{")) {
                JSONObject jsonObject = new JSONObject(msg);
                message = jsonObject.toString(KLog.JSON_INDENT);
            } else if (msg.startsWith("[")) {
                JSONArray jsonArray = new JSONArray(msg);
                message = jsonArray.toString(KLog.JSON_INDENT);
            } else {
                message = msg;
            }
        } catch (JSONException e) {
            message = msg;
        }

        Util.printLine(tag, true);
        message = headString + KLog.LINE_SEPARATOR + message;
        String[] lines = message.split(KLog.LINE_SEPARATOR);
        for (String line : lines) {
            Log.d(tag, "║ " + line);
        }
        Util.printLine(tag, false);
    }
}

代碼很簡單,就一個方法,但是在使用的時候,無論列印哪種格式,都是這樣使用的

//普通列印
 KLog.d(LOG_MSG);
 //JSON格式列印
 KLog.json(JSON);
 //XML格式列印
 KLog.xml(XML);

可以看到,雖然功能不同,但是都通過KLog這個類進行了封裝,用戶只知道用KLog這個類能完成所有需求即可,完全不需要知道代碼實現是幾個類完成的。

實際上,在KLog內部,是多個類共同完成列印功能的。

 private static void printLog(int type, String tagStr, Object... objects) {

        if (!IS_SHOW_LOG) {
            return;
        }

        String[] contents = wrapperContent(tagStr, objects);
        String tag = contents[0];
        String msg = contents[1];
        String headString = contents[2];

        switch (type) {
            case V:
            case D:
            case I:
            case W:
            case E:
            case A:
                BaseLog.printDefault(type, tag, headString + msg);
                break;
            case JSON:
                JsonLog.printJson(tag, msg, headString);
                break;
            case XML:
                XmlLog.printXml(tag, msg, headString);
                break;
        }
    }

但是通過外觀模式,這些細節對用戶隱藏了,這樣如果以後我想更換JSON的解析方式,用戶的代碼不需要任何改動,這也是這個設計模式的優勢所在。

總結

嘮嘮叨叨的,總算是把這幾種設計模式介紹完了,看完這篇文章,你應該就會發現其實Android中的設計模式確實到處都存在,不是缺少設計模式,而是缺少一雙發現的眼睛。

當然,設計模式的提出是為瞭解決特定的問題,當我們遇到類似問題的時候,可以從設計模式的角度思考和解決問題,這應該是我最大的收穫吧。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 今天上推特看見這篇文章,點進去發現是新貨。 正好最近想入Node的坑,又有一些Java基礎,所以希望翻譯出來給大家,同時也讓自己加深理解。 才疏學淺,如有不妥之處請指正。 原文鏈接: "Node for Java Developers" 這個 "Node University" 貌似也提供一些 ...
  • http://zhangzhaoaaa.iteye.com/blog/2123021 今天剛打個一個技術群,裡面有人問標題上的問題,嘿,我恰好遇過,現在大家至少也在用jquery1.9以上的版本,ajaxfileupload的版本早就不更新了,大家可以下載看:地址這裡,它例子里使用的Jquery是1 ...
  • 一、Unicode的一個小特性首先,Unicode規定了許多code point,每一個code point表示一個字元。如\u0033表示字元“3”,\u864e表示字元“你”。反過來,不是每一個字元都對應一個code point,每一個字元也不止有一個code point的表示方法。比如說,“�... ...
  • MainActivity.java main_activity.xml ...
  • 1.在代碼中添加這麼一行代碼 this.requestWindowFeature(Window.FEATURE_NO_TITLE); 註意:這段代碼需要寫在setContentView()之前,否則會報錯。還有一點,當你自己的Activity繼承自AppCompatActivity時,這種方法貌似並 ...
  • 服務不能自己運行,需要通過調用Context.startService()或Context.bindService()方法啟動服務。這兩個方法都 可以啟動Service,但是它們的使用場合有所不同。使用startService()方法啟用服務,調用者與服務之間沒有關連,即使調用者退出了,服 務仍然運 ...
  • //懶載入 //頂部需要拉伸自定義視圖 lazy var headView:MyHeaderView = { //let hframe = CGRect(x: 0, y: 0, width: swidth, height: swidth/self.imgRation) // let hview = ...
  • 記憶體管理是個永恆的話題! 記憶體溢出:就是分配的記憶體不足以放下數據項序列。如在一個域中輸入的數據超過了它的要求就會引發數據溢出問題,多餘的數據就可以作為指令在電腦上運行。就是你要求分配的記憶體超出了系統能給你的,系統不能滿足需求,於是產生溢出 記憶體泄漏:是指在堆上分配的記憶體沒有被釋放,從而失去對其控制 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...