android中常見(jiàn)的設(shè)計(jì)模式有哪些?

建造者模式

建造者模式最明顯的標(biāo)志就是Build類(lèi),而在Android中最常用的就是Dialog的構(gòu)建,Notification的構(gòu)建也是標(biāo)準(zhǔn)的建造者模式。

建造者模式很好理解,如果一個(gè)類(lèi)的構(gòu)造需要很多參數(shù),而且這些參數(shù)并不都是必須的,那么這種情況下就比較適合Builder。

比如構(gòu)建一個(gè)AlertDialog,標(biāo)題、內(nèi)容、取消按鈕、確定按鈕、中立按鈕,你可能只需要單獨(dú)設(shè)置幾個(gè)屬性即可;另外在我的OkHttpPlus項(xiàng)目中,構(gòu)造一個(gè)Http請(qǐng)求也是這樣的,有可能你只需要設(shè)置URL,有可能需要添加請(qǐng)求參數(shù)、Http Header等,這個(gè)時(shí)候建造者模式也是比較合適的。

單例模式

單例在Android開(kāi)發(fā)中經(jīng)常用到,但是表現(xiàn)形式可能不太一樣。

以ActivityManager等系統(tǒng)服務(wù)來(lái)說(shuō),是通過(guò)靜態(tài)代碼塊的形式實(shí)現(xiàn)單例,在首次加載類(lèi)文件時(shí),生成單例對(duì)象,然后保存在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());
                }});
    }
}

當(dāng)然,還有更加明顯的例子,比如AccessibilityManager內(nèi)部自己也保證了單例,使用getInstance獲取單例對(duì)象。

 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,默認(rèn)情況下在一個(gè)進(jìn)程中只存在一個(gè)實(shí)例,但是Application不能算是單例,因?yàn)樗臉?gòu)造方法未私有,你可以生成多個(gè)Application實(shí)例,但是沒(méi)有用,你沒(méi)有通過(guò)attach()綁定相關(guān)信息,沒(méi)有上下文環(huán)境。

public Application() {
        super(null);
    }

單例的使用場(chǎng)景也很簡(jiǎn)單,就是一個(gè)App只需要存在一個(gè)類(lèi)實(shí)例的情況,或者是類(lèi)的初始化操作比較耗費(fèi)資源的情況。在很多開(kāi)源框架中,我們只需要一個(gè)對(duì)象即可完成工作,比如各種網(wǎng)絡(luò)框架和圖片加載庫(kù)。

除此之外,因?yàn)閱卫膶?shí)現(xiàn)方式很多,比如懶漢式、餓漢式、靜態(tài)內(nèi)部類(lèi)、雙重鎖檢查、枚舉等方式,所以要清楚每種實(shí)現(xiàn)方式的主要特點(diǎn)和使用場(chǎng)景。

原型模式

原型模式在開(kāi)發(fā)中使用的并不多,但是在源碼中卻有所體現(xiàn)。

書(shū)中以Intent介紹了原型模式,是通過(guò)實(shí)現(xiàn)Cloneable接口來(lái)做的

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

其實(shí)這樣來(lái)看的話(huà),原型模式也比較好理解,就是你想更快的獲取到一個(gè)相同屬性的對(duì)象,那么就可以使用原型模式,比如這里就獲取到了一個(gè)Intent對(duì)象,Intent里面的屬性與被clone的相同,但是兩者并無(wú)關(guān)聯(lián),可以單獨(dú)使用。

除了實(shí)現(xiàn)Cloneable接口,你完全可以自己定義一個(gè)方法,來(lái)獲取一個(gè)對(duì)象。我這里以PhoneLayoutInflater為例子介紹。

PhoneLayoutInflater是LayoutInflater的子類(lèi),如果我們?cè)贏ctivity中獲取LayoutInflate的話(huà),是通過(guò)下面方法

 @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,就會(huì)調(diào)用cloneInContext(),這個(gè)方法在LayoutInflate是抽象方法,具體實(shí)現(xiàn)在PhoneLayoutInflater中

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

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

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

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

可以看到,實(shí)現(xiàn)和前面的完全相同,也是new了一個(gè)對(duì)象返回,因?yàn)镺kHttpClient的構(gòu)造過(guò)程比較復(fù)雜,參數(shù)眾多,所以用這種方式來(lái)直接生成新對(duì)象,成本很低,而且能保留之前對(duì)象的參數(shù)設(shè)置。

工廠(chǎng)方法模式

書(shū)中對(duì)于工廠(chǎng)方法模式的一個(gè)觀(guān)點(diǎn)很新奇,就是Activity.onCreate()可以看做是工廠(chǎng)方法模式,來(lái)生成不同的View對(duì)象填充界面。

但是我對(duì)這個(gè)說(shuō)法不太茍同,原因有兩點(diǎn):一是這種形式不太符合工廠(chǎng)方法,沒(méi)有抽象,沒(méi)有實(shí)現(xiàn),不符合一般格式,也不是靜態(tài)方法,不可看做是靜態(tài)工廠(chǎng)方法;二是沒(méi)有以生成對(duì)象為結(jié)果,即不是return view來(lái)生成對(duì)象,只是通過(guò)setContentView()來(lái)設(shè)置了屬性而已。這就像是給一個(gè)Activity設(shè)置了背景顏色一樣。當(dāng)然,設(shè)計(jì)模式這東西一個(gè)人有一個(gè)人的看法。

靜態(tài)工廠(chǎng)方法在Android中比較明顯的例子應(yīng)該就是BitmapFactory了,通過(guò)各種decodeXXX()就可以從不同渠道獲得Bitmap對(duì)象,這里不再贅述。

策略模式

在書(shū)中策略模式講得非常好,結(jié)合動(dòng)畫(huà)的插值器用法,我們可以很好的理解策略模式的形式和用法。

在我看來(lái),策略模式就相當(dāng)于一個(gè)影碟機(jī),你往里面插什么碟子,就能放出什么電影。

同樣,在OkHttpPlus的封裝中,為了對(duì)網(wǎng)絡(luò)返回值進(jìn)行解析,我使用了策略模式。當(dāng)然我寫(xiě)代碼的時(shí)候還不知道策略模式,是寫(xiě)完了之后突然想到,這就是策略模式??!

策略模式的精髓就在于,你傳入一個(gè)類(lèi),后面的處理就能按照這個(gè)類(lèi)的實(shí)現(xiàn)去做。以動(dòng)畫(huà)為例,設(shè)置不同的插值器對(duì)象,就可以得到不同的變化曲線(xiàn);以返回值解析為例,傳入什么樣的解析器,就可以把二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成什么格式的數(shù)據(jù),比如String、Json、XML。

責(zé)任鏈模式

書(shū)中對(duì)于責(zé)任鏈模式選取的例子非常有代表性,那就是Android的觸摸機(jī)制,這個(gè)看法讓我從另一個(gè)維度去理解Android中的觸摸事件傳遞。

我在這里提到這個(gè)模式,并不想說(shuō)太多,只是簡(jiǎn)單的推薦你讀一下這一章的內(nèi)容,相信你也會(huì)有收獲的。

觀(guān)察者模式

Android中的觀(guān)察者模式應(yīng)該是用的非常頻繁的一種模式了,對(duì)于這個(gè)模式的使用場(chǎng)景就一句話(huà):你想在某個(gè)對(duì)象發(fā)生變化時(shí),立刻收到通知。

書(shū)中介紹觀(guān)察者模式使用的是ListView的Adapter為例子,我之前知道Adapter屬于適配器模式,不知道這里還有觀(guān)察者模式的身影,學(xué)到了。

Android里面的各種監(jiān)聽(tīng)器,也都屬于觀(guān)察者模式,比如觸摸、點(diǎn)擊、按鍵等,ContentProvider和廣播接收者也有觀(guān)察者模式的身影,可以說(shuō)是無(wú)處不在。

除此之外,現(xiàn)在很多基于觀(guān)察者模式的第三方框架也是非常多,比如EventBus、RxJava等等,都是對(duì)觀(guān)察者模式的深入使用,感興趣的同學(xué)可以研究一下。

模板方法模式

這個(gè)模式我之前見(jiàn)的比較少,但是理解之后,就會(huì)發(fā)現(xiàn)這個(gè)模式很簡(jiǎn)單。

我覺(jué)得,模板方法模式的使用場(chǎng)景也是一句話(huà):流程確定,具體實(shí)現(xiàn)細(xì)節(jié)由子類(lèi)完成。

這里要關(guān)注一下『流程』這個(gè)關(guān)鍵字,隨便拿一個(gè)抽象類(lèi),都符合"具體實(shí)現(xiàn)細(xì)節(jié)由子類(lèi)完成"的要求,關(guān)鍵就在于是否有流程,有了流程,就叫模板方法模式,沒(méi)有流程,就是抽象類(lèi)的實(shí)現(xiàn)。

書(shū)中講這個(gè)模式用的是AsyncTask,各個(gè)方法之間的執(zhí)行符合流程,具體實(shí)現(xiàn)由我們完成,非常經(jīng)典。

另外一個(gè)方面,Activity的生命周期方法可以看做是模板方法模式,各個(gè)生命周期方法都是有順序的,具體實(shí)現(xiàn)我們可以重寫(xiě),是不是和前面的要求很符合?關(guān)于這方面的理解,可以參考我的這篇文章:對(duì)Activity生命周期的理解。

除了Android里面的模板方法模式,在其他開(kāi)源項(xiàng)目中也存在著這個(gè)模式的運(yùn)用。比如鴻洋的OkHttp-Utils項(xiàng)目,就是模板方法模式的典型實(shí)現(xiàn)。將一個(gè)Http請(qǐng)求的過(guò)程分割成幾部分,比如獲取URL,獲取請(qǐng)求頭,拼接請(qǐng)求信息等步驟,這幾個(gè)步驟之前有先后順序,就可以這樣來(lái)做。

代理模式和裝飾器模式

之所以把這兩個(gè)放在一起說(shuō),是因?yàn)檫@兩種模式很像,所以這里簡(jiǎn)單介紹下他們之間的區(qū)別,主要有兩點(diǎn)。

  1. 裝飾器模式關(guān)注于在一個(gè)對(duì)象上動(dòng)態(tài)的添加方法,而代理模式關(guān)注于控制對(duì)對(duì)象的訪(fǎng)問(wèn)
  2. 代理模式,代理類(lèi)可以對(duì)它的客戶(hù)隱藏一個(gè)對(duì)象的具體信息。因此,當(dāng)使用代理模式的時(shí)候,我們常常在一個(gè)代理類(lèi)中創(chuàng)建一個(gè)對(duì)象的實(shí)例。而當(dāng)我們使用裝飾器模式的時(shí)候,通常的做法是將原始對(duì)象作為一個(gè)參數(shù)傳給裝飾者的構(gòu)造器

這兩句話(huà)可能不太好理解,沒(méi)關(guān)系,下面看個(gè)例子。

代理模式會(huì)持有被代理對(duì)象的實(shí)例,而這個(gè)實(shí)例一般是作為成員變量直接存在于代理類(lèi)中的,即不需要額外的賦值。

比如說(shuō)WindowManagerImpl就是一個(gè)代理類(lèi),雖然名字上看著不像,但是它代理的是WindowManagerGlobal對(duì)象。從下面的代碼中就可以看出來(lái)。

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的方法都是通過(guò)WindowManagerGlobal實(shí)現(xiàn)的,而WindowManagerGlobal對(duì)象不需要額外的賦值,就存在于WindowManagerImpl中。另外,WindowManagerGlobal中其實(shí)有大量的方法,但是通過(guò)WindowManagerImpl代理之后,都沒(méi)有暴露出來(lái),對(duì)開(kāi)發(fā)者是透明的。

我們?cè)賮?lái)看一下裝飾器模式。裝飾器模式的目的不在于控制訪(fǎng)問(wèn),而是擴(kuò)展功能,相比于繼承基類(lèi)來(lái)擴(kuò)展功能,使用裝飾器模式更加的靈活。

書(shū)中是以Context和它的包裝類(lèi)ContextWrapper講解的,也非常的典型,我這里就不在贅述了,貼出一些代碼來(lái)說(shuō)明裝飾器模式的形式。

public class ContextWrapper extends Context {
    Context mBase;

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

但是還有一個(gè)問(wèn)題,就是在ContextWrapper中,所有方法的實(shí)現(xiàn)都是通過(guò)mBase來(lái)實(shí)現(xiàn)的,形式上是對(duì)上號(hào)了,說(shuō)好的擴(kuò)展功能呢?

功能擴(kuò)展其實(shí)是在ContextWrapper的子類(lèi)ContextThemeWrapper里面。

在ContextWrapper里面,獲取系統(tǒng)服務(wù)是直接通過(guò)mBase完成的

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

但是在ContextThemeWrapper里面,對(duì)這個(gè)方法進(jìn)行了重寫(xiě),完成了功能擴(kuò)展

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

當(dāng)然,如果不存在功能擴(kuò)展就不算是裝飾器模式了嗎?其實(shí)設(shè)計(jì)模式本來(lái)就是『仁者見(jiàn)仁,智者見(jiàn)智』的事情,只要你能理解這個(gè)意思就好。

外觀(guān)模式

外觀(guān)模式可能看到的比較少,但是其實(shí)不經(jīng)意間你就用到了。

這里以我的一個(gè)開(kāi)源項(xiàng)目KLog來(lái)說(shuō)吧,在最開(kāi)始寫(xiě)這個(gè)類(lèi)的時(shí)候,就只有KLog這一個(gè)類(lèi),完成基本的Log打印功能,后來(lái)又添加了JSON解析、XML解析、Log信息存儲(chǔ)等功能,這個(gè)時(shí)候一個(gè)類(lèi)就不太合適了,于是我把JSON、XML、FILE操作相關(guān)的代碼抽取到單獨(dú)的類(lèi)中,比如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);
    }
}

代碼很簡(jiǎn)單,就一個(gè)方法,但是在使用的時(shí)候,無(wú)論打印哪種格式,都是這樣使用的

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

可以看到,雖然功能不同,但是都通過(guò)KLog這個(gè)類(lèi)進(jìn)行了封裝,用戶(hù)只知道用KLog這個(gè)類(lèi)能完成所有需求即可,完全不需要知道代碼實(shí)現(xiàn)是幾個(gè)類(lèi)完成的。

實(shí)際上,在KLog內(nèi)部,是多個(gè)類(lèi)共同完成打印功能的。

 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;
        }
    }

但是通過(guò)外觀(guān)模式,這些細(xì)節(jié)對(duì)用戶(hù)隱藏了,這樣如果以后我想更換JSON的解析方式,用戶(hù)的代碼不需要任何改動(dòng),這也是這個(gè)設(shè)計(jì)模式的優(yōu)勢(shì)所在。

總結(jié)

嘮嘮叨叨的,總算是把這幾種設(shè)計(jì)模式介紹完了,看完這篇文章,你應(yīng)該就會(huì)發(fā)現(xiàn)其實(shí)Android中的設(shè)計(jì)模式確實(shí)到處都存在,不是缺少設(shè)計(jì)模式,而是缺少一雙發(fā)現(xiàn)的眼睛。

當(dāng)然,設(shè)計(jì)模式的提出是為了解決特定的問(wèn)題,當(dāng)我們遇到類(lèi)似問(wèn)題的時(shí)候,可以從設(shè)計(jì)模式的角度思考和解決問(wèn)題,這應(yīng)該是我最大的收獲吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,881評(píng)論 25 709
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,534評(píng)論 19 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,332評(píng)論 6 13
  • 習(xí)慣了所有的碎片時(shí)間被智能手機(jī)所綁架;習(xí)慣了無(wú)休止的從互聯(lián)網(wǎng)獲取資訊;習(xí)慣了忙碌而忽視思考;習(xí)慣了應(yīng)付而少了反...
    Henry_Zhu閱讀 200評(píng)論 0 0
  • 讀書(shū)心得 各位老師大家下午好: 很高興今天有機(jī)會(huì)和大家在這里分享一些我的讀書(shū)心得。我真正意義上的讀書(shū)是從小學(xué)三年級(jí)...
    拾起曾經(jīng)閱讀 927評(píng)論 0 0

友情鏈接更多精彩內(nèi)容