建造者模式
建造者模式最明顯的標(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)。
- 裝飾器模式關(guān)注于在一個(gè)對(duì)象上動(dòng)態(tài)的添加方法,而代理模式關(guān)注于控制對(duì)對(duì)象的訪(fǎng)問(wèn)
- 代理模式,代理類(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)該是我最大的收獲吧。