尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️
Linux編程
點擊右側關注,免費入門到精通!
作者丨安卓巴士Android開發者門戶
https://mp.weixin.qq.com/s/MoUTuzEyMoQngv_Ud3zLJw
建造者模式
建造者模式最明顯的標誌就是Build類,而在Android中最常用的就是Dialog的構建,Notification的構建也是標準的建造者模式。
建造者模式很好理解,如果一個類的構造需要很多參數,而且這些參數並不都是必須的,那麼這種情況下就比較適合Builder。
比如構建一個AlertDialog,標題、內容、取消按鈕、確定按鈕、中立按鈕,你可能只需要單獨設置幾個屬性即可;另外在我的OkHttpPlus項目中,構造一個Http請求也是這樣的,有可能你只需要設置URL,有可能需要添加請求參數、Http Header等,這個時候建造者模式也是比較合適的。
單例模式
單例在Android開發中經常用到,但是表現形式可能不太一樣。
以ActivityManager等系統服務來說,是通過靜態代碼塊的形式做到單例,在首次加載類文件時,生成單例對象,然後保存在Cache中,之後的使用都是直接從Cache中獲取。
classContextImplextendsContext{static{registerService(ACTIVITY_SERVICE,newServiceFetcher(){publicObjectcreateService(ContextImplctx){returnnewActivityManager(ctx.getOuterContext(),ctx.mMainThread.getHandler());}});}}
當然,還有更加明顯的例子,比如AccessibilityManager內部自己也保證了單例,使用getInstance獲取單例對象。
publicstaticAccessibilityManagergetInstance(Contextcontext){synchronized(sInstanceSync){if(sInstance==null){......IBinderiBinder=ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);IAccessibilityManagerservice=iBinder==null?null:IAccessibilityManager.Stub.asInterface(iBinder);sInstance=newAccessibilityManager(context,service,userId);}}returnsInstance;}
除此之外,還有一些偽單例,比如Application,默認情況下在一個進程中只存在一個實例,但是Application不能算是單例,因為它的構造方法未私有,你可以生成多個Application實例,但是沒有用,你沒有通過attach()綁定相關信息,沒有上下文環境。
publicApplication(){super(null);}
單例的使用場景也很簡單,就是一個App只需要存在一個類實例的情況,或者是類的初始化操作比較耗費資源的情況。在很多開源框架中,我們只需要一個對象即可完成工作,比如各種網路框架和圖片加載庫。
除此之外,因為單例的做到方式很多,比如懶漢式、餓漢式、靜態內部類、雙重鎖檢查、枚舉等方式,所以要清楚每種做到方式的主要特點和使用場景。
原型模式
原型模式在開發中使用的並不多,但是在源碼中卻有所體現。
書中以Intent介紹了原型模式,是通過做到Cloneable接口來做的
publicclassIntentimplementsParcelable,Cloneable{@OverridepublicObjectclone(){returnnewIntent(this);}}
其實這樣來看的話,原型模式也比較好理解,就是你想更快的獲取到一個相同屬性的對象,那麼就可以使用原型模式,比如這里就獲取到了一個Intent對象,Intent里面的屬性與被clone的相同,但是兩者並無關聯,可以單獨使用。
除了做到Cloneable接口,你完全可以自己定義一個方法,來獲取一個對象。我這里以PhoneLayoutInflater為例子介紹。
PhoneLayoutInflater是LayoutInflater的子類,如果我們在Activity中獲取LayoutInflate的話,是通過下面方法
@OverridepublicObjectgetSystemService(Stringname){if(LAYOUT_INFLATER_SERVICE.equals(name)){if(mInflater==null){mInflater=LayoutInflater.from(getBaseContext()).cloneInContext(this);}returnmInflater;}returngetBaseContext().getSystemService(name);}
可以看到,如果為null,就會調用cloneInContext(),這個方法在LayoutInflate是抽象方法,具體做到在PhoneLayoutInflater中
publicLayoutInflatercloneInContext(ContextnewContext){returnnewPhoneLayoutInflater(this,newContext);}
可以看到,這也是一個原型模式,所以我們不要太糾結於形式,更重要的是理解這樣做的好處。
除了在源碼中可以看到原型模式,在開源框架中也可以看到,比如OkHttpClient中就存在著下面的方法
/**ReturnsashallowcopyofthisOkHttpClient.*/@OverridepublicOkHttpClientclone(){returnnewOkHttpClient(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,各個方法之間的執行符合流程,具體做到由我們完成,非常經典。
除了Android里面的模板方法模式,在其他開源項目中也存在著這個模式的運用。比如鴻洋的OkHttp-Utils項目,就是模板方法模式的典型做到。將一個Http請求的過程分割成幾部分,比如獲取URL,獲取請求頭,拼接請求信息等步驟,這幾個步驟之前有先後順序,就可以這樣來做。
代理模式和裝飾器模式
之所以把這兩個放在一起說,是因為這兩種模式很像,所以這里簡單介紹下他們之間的區別,主要有兩點。
裝飾器模式關注於在一個對象上動態的添加方法,而代理模式關注於控制對對象的訪問
代理模式,代理類可以對它的客戶隱藏一個對象的具體信息。因此,當使用代理模式的時候,我們常常在一個代理類中創建一個對象的實例。而當我們使用裝飾器模式的時候,通常的做法是將原始對象作為一個參數傳給裝飾者的構造器
這兩句話可能不太好理解,沒關係,下面看個例子。
代理模式會持有被代理對象的實例,而這個實例一般是作為成員變量直接存在於代理類中的,即不需要額外的賦值。
比如說WindowManagerImpl就是一個代理類,雖然名字上看著不像,但是它代理的是WindowManagerGlobal對象。從下面的代碼中就可以看出來。
publicfinalclassWindowManagerImplimplementsWindowManager{privatefinalWindowManagerGlobalmGlobal=WindowManagerGlobal.getInstance();privatefinalDisplaymDisplay;privatefinalWindowmParentWindow;......@OverridepublicvoidaddView(Viewview,ViewGroup.LayoutParamsparams){mGlobal.addView(view,params,mDisplay,mParentWindow);}@OverridepublicvoidupdateViewLayout(Viewview,ViewGroup.LayoutParamsparams){mGlobal.updateViewLayout(view,params);}@OverridepublicvoidremoveView(Viewview){mGlobal.removeView(view,false);}@OverridepublicvoidremoveViewImmediate(Viewview){mGlobal.removeView(view,true);}@OverridepublicDisplaygetDefaultDisplay(){returnmDisplay;}}
從上面的代碼中可以看出,大部分WindowManagerImpl的方法都是通過WindowManagerGlobal做到的,而WindowManagerGlobal對象不需要額外的賦值,就存在於WindowManagerImpl中。另外,WindowManagerGlobal中其實有大量的方法,但是通過WindowManagerImpl代理之後,都沒有暴露出來,對開發者是透明的。
我們再來看一下裝飾器模式。裝飾器模式的目的不在於控制訪問,而是擴展功能,相比於繼承基類來擴展功能,使用裝飾器模式更加的靈活。
書中是以Context和它的包裝類ContextWrapper講解的,也非常的典型,我這里就不在贅述了,貼出一些代碼來說明裝飾器模式的形式。
publicclassContextWrapperextendsContext{ContextmBase;publicContextWrapper(Contextbase){mBase=base;}}
但是還有一個問題,就是在ContextWrapper中,所有方法的做到都是通過mBase來做到的,形式上是對上號了,說好的擴展功能呢?
功能擴展其實是在ContextWrapper的子類ContextThemeWrapper里面。
在ContextWrapper里面,獲取系統服務是直接通過mBase完成的
@OverridepublicObjectgetSystemService(Stringname){returnmBase.getSystemService(name);}
但是在ContextThemeWrapper里面,對這個方法進行了重寫,完成了功能擴展
@OverridepublicObjectgetSystemService(Stringname){if(LAYOUT_INFLATER_SERVICE.equals(name)){if(mInflater==null){mInflater=LayoutInflater.from(getBaseContext()).cloneInContext(this);}returnmInflater;}returngetBaseContext().getSystemService(name);}
當然,如果不存在功能擴展就不算是裝飾器模式了嗎?其實設計模式本來就是『仁者見仁,智者見智』的事情,只要你能理解這個意思就好。
外觀模式
外觀模式可能看到的比較少,但是其實不經意間你就用到了。
這里以我的一個開源項目KLog來說吧,在最開始寫這個類的時候,就只有KLog這一個類,完成基本的Log列印功能,後來又添加了JSON解析、XML解析、Log信息存儲等功能,這個時候一個類就不太合適了,於是我把JSON、XML、FILE操作相關的代碼抽取到單獨的類中,比如JSON列印的代碼
publicclassJsonLog{publicstaticvoidprintJson(Stringtag,Stringmsg,StringheadString){Stringmessage;try{if(msg.startsWith("{")){JSONObjectjsonObject=newJSONObject(msg);message=jsonObject.toString(KLog.JSON_INDENT);}elseif(msg.startsWith("[")){JSONArrayjsonArray=newJSONArray(msg);message=jsonArray.toString(KLog.JSON_INDENT);}else{message=msg;}}catch(JSONExceptione){message=msg;}Util.printLine(tag,true);message=headString+KLog.LINE_SEPARATOR+message;String[]lines=message.split(KLog.LINE_SEPARATOR);for(Stringline:lines){Log.d(tag,"║"+line);}Util.printLine(tag,false);}}
代碼很簡單,就一個方法,但是在使用的時候,無論列印哪種格式,都是這樣使用的
//普通列印KLog.d(LOG_MSG);//JSON格式列印KLog.json(JSON);//XML格式列印KLog.xml(XML);
可以看到,雖然功能不同,但是都通過KLog這個類進行了封裝,用戶只知道用KLog這個類能完成所有需求即可,完全不需要知道代碼做到是幾個類完成的。
實際上,在KLog內部,是多個類共同完成列印功能的。
privatestaticvoidprintLog(inttype,StringtagStr,Object...objects){if(!IS_SHOW_LOG){return;}String[]contents=wrapperContent(tagStr,objects);Stringtag=contents[0];Stringmsg=contents[1];StringheadString=contents[2];switch(type){caseV:caseD:caseI:caseW:caseE:caseA:BaseLog.printDefault(type,tag,headString+msg);break;caseJSON:JsonLog.printJson(tag,msg,headString);break;caseXML:XmlLog.printXml(tag,msg,headString);break;}}
但是通過外觀模式,這些細節對用戶隱藏了,這樣如果以後我想更換JSON的解析方式,用戶的代碼不需要任何改動,這也是這個設計模式的優勢所在。
總結
嘮嘮叨叨的,總算是把這幾種設計模式介紹完了,看完這篇文章,你應該就會發現其實Android中的設計模式確實到處都存在,不是缺少設計模式,而是缺少一雙發現的眼睛。
當然,設計模式的提出是為了解決特定的問題,當我們遇到類似問題的時候,可以從設計模式的角度思考和解決問題,這應該是我最大的收獲吧。
推薦↓↓↓
長
按
關
註
?【16個技術公眾號】都在這里!
涵蓋:工程師大咖、源碼共讀、工程師共讀、數據結構與算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、數據庫研發、幽默工程師等。
萬水千山總是情,點個 「好看」 行不行