從Android代碼中來記憶23種設計模式

相信大家都曾經下定決心把23種設計模式牢記于心,每次看完之后過一段時間又忘記了~,又得回去看,腦子里唯一依稀記得的是少數設計模式的大致的定義。其實,網上很多文章講得都非常好,我也曾經去看過各種文章。也曾一直苦惱這些難以永久記下的設計模式,直到我接觸到了《Android源碼設計模式解析與實戰》——何紅輝與關愛明著,發現原來其實我們在Android中都接觸過這些設計模式,只是我們不知道而已。既然我們都接觸過,我們只需一一對號入座,對設計模式的記憶就不用死記硬背了!這里自愿無償做個廣告,《Android源碼設計模式解析與實戰》這本書真心不錯,每個Android程序員最好都去翻翻...正如你所想的那樣,本文是從這本書中的總結,相信你也會跟我一樣,從中獲益。

面向對象的六大原則

首先,我們為什么要學習設計模式。主要是這些模式是前人總結的經驗,使用這些模式能讓我們的程序更健壯、更穩定、容易擴展等等優點。在編寫面向對象程序時,我們需要遵循以下6個原則,能讓我們的程序維護起來更輕松~(當然還有其它好處)。

1 單一職責原則

單一原則很簡單,就是將一組相關性很高的函數、數據封裝到一個類中。換句話說,一個類應該有職責單一。

2 開閉原則

開閉原則理解起來也不復雜,就是一個類應該對于擴展是開放的,但是對于修改是封閉的。我們知道,在開放的app或者是系統中,經常需要升級、維護等,這就要對原來的代碼進行修改,可是修改時容易破壞原有的系統,甚至帶來一些新的難以發現的BUG。因此,我們在一開始編寫代碼時,就應該注意盡量通過擴展的方式實現新的功能,而不是通過修改已有的代碼實現。

3 里氏替換原則

里氏替換原則的定義為:所有引用基類的地方必須能透明地使用其子類對象。定義看起來很抽象,其實,很容易理解,本質上就是說,要好好利用繼承和多態。簡單地說,就是以父類的形式聲明的變量(或形參),賦值為任何繼承于這個父類的子類后不影響程序的執行。看一組代碼你就明白這個原則了:

//窗口類publicclassWindow(){publicvoidshow(View child){? ? ? ? child.draw();? ? }}publicabstractclassView(){publicabstractvoiddraw();publicvoidmeasure(intwidht,intheight){//測量視圖大小}}publicclassButtonextendsView{publicvoiddraw(){//繪制按鈕}}publicclassTextViewextendsView{publicvoiddraw(){//繪制文本}}

Window 類中show函數需要傳入View,并且調用View對象的draw函數。而每個繼承于View的子對象都有draw的實現,不存在繼承于View但是卻沒實現draw函數的子類(abstract方法必須實現)。我們在抽象類設計之時就運用到了里氏替換原則。

4 依賴倒置原則

依賴倒置主要是實現解耦,使得高層次的模塊不依賴于低層次模塊的具體實現細節。怎么去理解它呢,我們需要知道幾個關鍵點:

(1)高層模塊不應該依賴底層模塊(具體實現),二者都應該依賴其抽象(抽象類或接口)

(2)抽象不應該依賴細節(廢話,抽象類跟接口肯定不依賴具體的實現了)

(3)細節應該依賴于抽象(同樣廢話,具體實現類肯定要依賴其繼承的抽象類或接口)

其實,在我們用的Java語言中,抽象就是指接口或者抽象類,二者都是不能直接被實例化;細節就是實現類,實現接口或者繼承抽象類而產生的類,就是細節。使用Java語言描述就簡單了:就是各個模塊之間相互傳遞的參數聲明為抽象類型,而不是聲明為具體的實現類;

5 接口隔離原則

接口隔離原則定義:類之間的依賴關系應該建立在最小的接口上。其原則是將非常龐大的、臃腫的接口拆分成更小的更具體的接口。

6 迪米特原則

描述的原則:一個對象應該對其他的對象有最少的了解。什么意思呢?就是說一個類應該對自己調用的類知道的最少。還是不懂?其實簡單來說:假設類A實現了某個功能,類B需要調用類A的去執行這個功能,那么類A應該只暴露一個函數給類B,這個函數表示是實現這個功能的函數,而不是讓類A把實現這個功能的所有細分的函數暴露給B。

開始學設計模式

學習了上面的六大原則之后,提前做了預熱。現在開始,一起學習設計模式吧~

1 單例模式

單例模式可以說是最容易理解的模式了,也是應用最廣的模式之一,先看看定義吧。

定義:確保單例類只有一個實例,并且這個單例類提供一個函數接口讓其他類獲取到這個唯一的實例。

什么時候需要使用單例模式呢:如果某個類,創建時需要消耗很多資源,即new出這個類的代價很大;或者是這個類占用很多內存,如果創建太多這個類實例會導致內存占用太多。

關于單例模式,雖然很簡單,無需過多的解釋,但是這里還要提個醒,其實單例模式里面有很多坑。我們去會會單例模式。最簡單的單例模式如下:

publicclassSingleton{privatestaticSingleton instance;//將默認的構造函數私有化,防止其他類手動newprivateSingleton(){};publicstaticSingletongetInstance(){if(instance==null)? ? ? ? ? ? instance=newSingleton();returninstatnce;? ? }}

如果是單線程下的系統,這么寫肯定沒問題。可是如果是多線程環境呢?這代碼明顯不是線程安全的,存在隱患:某個線程拿到的instance可能是null,可能你會想,這有什么難得,直接在getInstance()函數上加sychronized關鍵字不就好了。可是你想過沒有,每次調用getInstance()時都要執行同步,這帶來沒必要的性能上的消耗。注意,在方法上加sychronized關鍵字時,一個線程訪問這個方法時,其他線程無法同時訪問這個類其他sychronized方法。的我們看看另外一種實現:

publicclassSingleton{privatestaticSingleton instance;//將默認的構造函數私有化,防止其他類手動newprivateSingleton(){};publicstaticSingletongetInstance(){if(instance==null){? ? ? ? ? ? sychronized(Singleton.class){if(instance==null)? ? ? ? ? ? ? ? ? ? instance=newSingleton();? ? ? ? ? ? }? ? ? ? }returninstatnce;? ? }}

為什么需要2次判斷是否為空呢?第一次判斷是為了避免不必要的同步,第二次判斷是確保在此之前沒有其他線程進入到sychronized塊創建了新實例。這段代碼看上去非常完美,但是,,,卻有隱患!問題出現在哪呢?主要是在instance=new Singleton();這段代碼上。這段代碼會編譯成多條指令,大致上做了3件事:

(1)給Singleton實例分配內存

(2)調用Singleton()構造函數,初始化成員字段

(3)將instance對象指向分配的內存(此時instance就不是null啦~)

上面的(2)和(3)的順序無法得到保證的,也就是說,JVM可能先初始化實例字段再把instance指向具體的內存實例,也可能先把instance指向內存實例再對實例進行初始化成員字段。考慮這種情況:一開始,第一個線程執行instance=new Singleton();這句時,JVM先指向一個堆地址,而此時,又來了一個線程2,它發現instance不是null,就直接拿去用了,但是堆里面對單例對象的初始化并沒有完成,最終出現錯誤~ 。

看看另外一種方式:

publicclassSingleton{privatevolatilestaticSingleton instance;//將默認的構造函數私有化,防止其他類手動newprivateSingleton(){};publicstaticSingletongetInstance(){if(instance==null){? ? ? ? ? ? sychronized(Singleton.class){if(instance==null)? ? ? ? ? ? ? ? ? ? instance=newSingleton();? ? ? ? ? ? }? ? ? ? }returninstatnce;? ? }}

相比前面的代碼,這里只是對instance變量加了一個volatile關鍵字volatile關鍵字的作用是:線程每次使用到被volatile關鍵字修飾的變量時,都會去堆里拿最新的數據。換句話說,就是每次使用instance時,保證了instance是最新的。注意:volatile關鍵字并不能解決并發的問題,關于volatile請查看其它相關文章。但是volatile能解決我們這里的問題。

那么在安卓中哪些地方用到了單例模式呢?其實,我們在調用系統服務時拿到的Binder對象就是個單例。比如:

//獲取WindowManager服務引用WindowManager wm = (WindowManager)getSystemService(getApplication().WINDOW_SERVICE);

其內部是通過單例的方式返回的,由于單例模式較簡單,這里不去深究。

2 Builder模式

Builder模式是什么情況呢?我不想去提它的定義,因為他的定義:將一個復雜對象的構造與它的表示分離,使得同樣的構造過程可以創建不同的表示。好吧,我還是提了。但是看了這個定義并沒有什么luan用。我們看看具體在什么情況下用到Builder模式:主要是在創建某個對象時,需要設定很多的參數(通過setter方法),但是這些參數必須按照某個順序設定,或者是設置步驟不同會得到不同結果。舉個非常簡單的例子:

publicclassMyData{privateintid;privateString num;publicvoidTest(){? ? ? ? ? ? }publicvoidsetId(intid){this.id=id;? ? }publicvoidsetNum(String num){this.num=num+"id";? ? }? ? ? }

當然了,沒有人會這么去寫代碼。這里只是舉例子,或者是有時候很多參數有這種類似的依賴關系時,通過構造函數未免太多參數了。回到主題,就是如果是上面的代碼,該怎么辦呢?你可能會說,那還不簡單,先調用setId函數,再調用setNum函數。是的,沒錯。可是,萬一你一不小心先調用了setNum呢?這是比較簡單的示例,如果是比較復雜的,有很多變量之間依賴的關系,那你每次都得小心翼翼的把各個函數的執行步驟寫正確。

我們看看Builder模式是怎么去做的:

publicclassMyBuilder{privateintid;privateString num;publicMyDatabuild(){? ? ? ? MyData d=newMyData();? ? ? ? d.setId(id);? ? ? ? d.setNum(num);returnt;? ? }publicMyBuildersetId(intid){this.id=id;returnthis;? ? }publicMyBuildersetNum(String num){this.num=num;returnthis;? ? }}publicclassTest{publicstaticvoidmain(String[] args){? ? ? ? MyData d=newMyBuilder().setId(10).setNum("hc").build();? ? }}

注意到,Builer類的setter函數都會返回自身的引用this,這主要是用于鏈式調用,這也是Builder設計模式中的一個很明顯的特征。

Android中用過的代碼來記憶

記憶我這個例子沒啥意義,我們前面說過,要通過Android中用過的代碼來記憶,這樣才可以不用死記硬背。那么在Android中哪里用到了Builder設計模式呢?哈哈~在創建對話框時,是不是跟上面有點類似呢?

AlertDialog.Builer builder=newAlertDialog.Builder(context);builder.setIcon(R.drawable.icon)? ? .setTitle("title")? ? .setMessage("message")? ? .setPositiveButton("Button1",newDialogInterface.OnclickListener(){publicvoidonClick(DialogInterface dialog,intwhichButton){? ? ? ? ? ? ? ? setTitle("click");? ? ? ? ? ? }? ? ? ? ? })? ? .create()? ? .show();

這里的create()函數就想到上面代碼中的build函數。看到這里是不是在內心中默默的把Builder設計模式拿下了?你并不用死記硬背~

3 原型模式

原型設計模式非常簡單,就是將一個對象進行拷貝。對于類A實例a,要對a進行拷貝,就是創建一個跟a一樣的類型A的實例b,然后將a的屬性全部復制到b。

什么時候會用到原型模式呢?我個人認為,可以在類的屬性特別多,但是又要經常對類進行拷貝的時候可以用原型模式,這樣代碼比較簡潔,而且比較方便。

另外要注意的是,還有深拷貝和淺拷貝。深拷貝就是把對象里面的引用的對象也要拷貝一份新的對象,并將這個新的引用對象作為拷貝的對象引用。說的比較繞哈~,舉個例子,假設A類中有B類的引用b,現在需要對A類實例進行拷貝,那么深拷貝就是,先對b進行一次拷貝得到nb,然后把nb作為A類拷貝的對象的引用,如此一層一層迭代拷貝,把所有的引用都拷貝結束。淺拷貝則不是。

原型模式比較簡單,看看Android怎么運用原型模式:

Uri uri=Uri.parse("smsto:10086");Intent shareIntent=newIntent(Intent.ACTION_SENDTO,uri);//克隆副本Intent intent=(Intetn)shareIntent.clone();startActivity(intent);

或許我們平時不會這么去寫,但是Intent對象確實提供了原型模式的函數clone()

4 工廠方法模式

定義:定義一個創建對象的接口,讓子類決定實例化哪個類

先看一個例子:

publicabstractclassProduct{publicabstractvoidmethod();}publicclassConcreteProductAextendsProdect{publicvoidmethod(){? ? ? ? System.out.println("我是產品A!");? ? }}publicclassConcreteProductBextendsProdect{publicvoidmethod(){? ? ? ? System.out.println("我是產品B!");? ? }}publicabstractclassFactory{publicabstractProductcreateProduct();}publicclassMyFactoryextendsFactory{publicProductcreateProduct(){returnnewConcreteProductA();? ? }}

看到上面的代碼,是不是覺得工廠模式很簡單呢?還可以通過傳參的方式,讓MyFactory的createProduct方法根據傳入的參數決定是創建ConcreteProductA還是ConcreteProductB。

同樣的,我們不希望記住這個例子,而是通過Android中的代碼來記憶:

其實,在getSystemService方法中就是用到了工廠模式,他就是根據傳入的參數決定創建哪個對象,當然了,由于返回的都是以單例模式存在的對象,因此不用new了,直接把單例返回就好。

publicObjectgetSystemService(String name){if(getBaseContext() ==null) {thrownewIllegalStateException("System services not available to Activities before onCreate()");? ? }//........if(WINDOW_SERVICE.equals(name)) {returnmWindowManager;? ? }elseif(SEARCH_SERVICE.equals(name)) {? ? ? ? ensureSearchManager();returnmSearchManager;? ? }//.......returnsuper.getSystemService(name);? }

5 抽象工廠模式

抽象工廠模式:為創建一組相關或者是相互依賴的對象提供一個接口,而不需要制定他們的具體類

看個例子吧,將它跟工廠方法模式做個對比:

publicabstractclassAbstractProductA{publicabstractvoidmethod();}publicabstractclassAbstractProdectB{publicabstractvoidmethod();}publicclassConcreteProductA1extendsAbstractProductA{publicvoidmethod(){? ? ? ? System.out.println("具體產品A1的方法!");? ? }}publicclassConcreteProductA2extendsAbstractProductA{publicvoidmethod(){? ? ? ? System.out.println("具體產品A2的方法!");? ? }}publicclassConcreteProductB1extendsAbstractProductB{publicvoidmethod(){? ? ? ? System.out.println("具體產品B1的方法!");? ? }}publicclassConcreteProductB2extendsAbstractProductB{publicvoidmethod(){? ? ? ? System.out.println("具體產品B2的方法!");? ? }}publicabstractclassAbstractFactory{publicabstractAbstractProductAcreateProductA();publicabstractAbstractProductBcreateProductB();}publicclassConcreteFactory1extendsAbstractFactory{publicAbstractProductAcreateProductA(){returnnewConcreteProductA1();? ? }publicAbstractProductBcreateProductB(){returnnewConcreteProductB1();? ? }}publicclassConcreteFactory2extendsAbstractFactory{publicAbstractProductAcreateProductA(){returnnewConcreteProductA2();? ? }publicAbstractProductBcreateProductB(){returnnewConcreteProductB2();? ? }}

其實Android源碼中對抽象工廠出現的比較少,好在抽象工廠方法并不復雜,很容易記住,我們可以從Service中去理解,Service的onBind方法可以看成是一個工廠方法,從framework角度來看Service,可以看成是一個具體的工廠,這相當于一個抽象工廠方法模式的雛形。

publicclassBaseServiceextendsService{@Nullable@OverridepublicIBinderonBind(Intent intent){returnnewBinder();? ? }? ? }

6 策略模式

定義:有一系列的算法,將每個算法封裝起來(每個算法可以封裝到不同的類中),各個算法之間可以替換,策略模式讓算法獨立于使用它的客戶而獨立變化。

舉個例子來理解吧,比如,你現在又很多排序算法:冒泡、希爾、歸并、選擇等等。我們要根據實際情況來選擇使用哪種算法,有一種常見的方法是,通過if...else或者case...等條件判斷語句來選擇。但是這個類的維護成本會變高,維護時也容易發生錯誤。

如何使用策略模式呢,我不打算寫示例代碼了,簡單描述一下,就將前面說的算法選擇進行描述。我們可以定義一個算法抽象類AbstractAlgorithm,這個類定義一個抽象方法sort()。每個具體的排序算法去繼承AbstractAlgorithm類并重寫sort()實現排序。在需要使用排序的類Client類中,添加一個setAlgorithm(AbstractAlgorithm al);方法將算法設置進去,每次Client需要排序而是就調用al.sort()。

不知道簡單描述能不能讓你理解~

看看Android中哪里出現了策略模式,其中在屬性動畫中使用時間插值器的時候就用到了。在使用動畫時,你可以選擇線性插值器LinearInterpolator、加速減速插值器AccelerateDecelerateInterpolator、減速插值器DecelerateInterpolator以及自定義的插值器。這些插值器都是實現根據時間流逝的百分比來計算出當前屬性值改變的百分比。通過根據需要選擇不同的插值器,實現不同的動畫效果。這些比較好理解,就不去粘貼Android源碼了。

7 狀態模式

狀態模式中,行為是由狀態來決定的,不同狀態下有不同行為。狀態模式和策略模式的結構幾乎是一模一樣的,主要是他們表達的目的和本質是不同。狀態模式的行為是平行的、不可替換的,策略模式的行為是彼此獨立可相互替換的。

舉個例子把,比如電視,電視有2個狀態,一個是開機,一個是關機,開機時可以切換頻道,關機時切換頻道不做任何響應。

publicinterfaceTvState{publicvoidnextChannerl();publicvoidprevChannerl();publicvoidturnUp();publicvoidturnDown();}publicclassPowerOffStateimplementsTvState{publicvoidnextChannel(){}publicvoidprevChannel(){}publicvoidturnUp(){}publicvoidturnDown(){}? ? }publicclassPowerOnStateimplementsTvState{publicvoidnextChannel(){? ? ? ? System.out.println("下一頻道");? ? }publicvoidprevChannel(){? ? ? ? System.out.println("上一頻道");? ? }publicvoidturnUp(){? ? ? ? System.out.println("調高音量");? ? }publicvoidturnDown(){? ? ? ? System.out.println("調低音量");? ? }? ? }publicinterfacePowerController{publicvoidpowerOn();publicvoidpowerOff();}publicclassTvControllerimplementsPowerController{? ? TvState mTvState;publicvoidsetTvState(TvStete tvState){? ? ? ? mTvState=tvState;? ? }publicvoidpowerOn(){? ? ? ? setTvState(newPowerOnState());? ? ? ? System.out.println("開機啦");? ? }publicvoidpowerOff(){? ? ? ? setTvState(newPowerOffState());? ? ? ? System.out.println("關機啦");? ? }publicvoidnextChannel(){? ? ? ? mTvState.nextChannel();? ? }publicvoidprevChannel(){? ? ? ? mTvState.prevChannel();? ? }publicvoidturnUp(){? ? ? ? mTvState.turnUp();? ? }publicvoidturnDown(){? ? ? ? mTvState.turnDown();? ? }? ? }publicclassClient{publicstaticvoidmain(String[] args){? ? ? ? TvController tvController=newTvController();? ? ? ? tvController.powerOn();? ? ? ? tvController.nextChannel();? ? ? ? tvController.turnUp();? ? ? ? ? ? ? ? tvController.powerOff();//調高音量,此時不會生效tvController.turnUp();? ? }}

在Android源碼中,哪里有用到狀態模式呢?其實很多地方用到了,舉一個地方例子,就是WIFI管理模塊。當WIFI開啟時,自動掃描周圍的接入點,然后以列表的形式展示;當wifi關閉時則清空。這里wifi管理模塊就是根據不同的狀態執行不同的行為。由于代碼太多,我就不手打敲入了~我們只要知道大致Android里面在哪里用到了以及大概是怎么用的就好。

8 責任鏈模式

定義:使多個對象都有機會處理請求,從而避免請求的發送者和接受者直接的耦合關系,將這些對象連成一條鏈,并沿這條鏈傳遞該請求,直到有對象處理它為止。

相信聰明的你很容易理解吧,基本不需要例子來解釋了,直接進如到Android源碼中哪里用到了責任鏈:在Android處理點擊事件時,父View先接收到點擊事件,如果父View不處理則交給子View,依次往下傳遞~

9 解釋器模式

定義:給定一個語言,定義它的語法,并定義一個解釋器,這個解釋器用于解析語言。

從定義中看起來比較抽象,其實,很簡單,很容易理解!就是相當于自定義一個格式的文件,然后去解析它。不用理解的那么復雜!

我們看看Android中哪里用到了,從我們第一次學Android時就知道,四大組件需要在AndroidManifest.xml中定義,其實AndroidManifest.xml就定義了,等標簽(語句)的屬性以及其子標簽,規定了具體的使用(語法),通過PackageManagerService(解釋器)進行解析。

10 命令模式

定義:命令模式將每個請求封裝成一個對象,從而讓用戶使用不同的請求把客戶端參數化;將請求進行排隊或者記錄請求日志,以及支持可撤銷操作。

舉個例子來理解:當我們點擊“關機”命令,系統會執行一系列操作,比如暫停事件處理、保存系統配置、結束程序進程、調用內核命令關閉計算機等等,這些命令封裝從不同的對象,然后放入到隊列中一個個去執行,還可以提供撤銷操作。

那么Android中哪里用到了命令模式呢?在framework層還真不多。但是在底層卻用到了,一個比較典型的例子就是在Android事件機制中,底層邏輯對事件的轉發處理。每次的按鍵事件會被封裝成NotifyKeyArgs對象。通過InputDispatcher封裝具體的事件操作。

11 觀察者模式

定義:定義了對象之間的一對多的關系,其實就是1對n,當“1”發生變化時,“n”全部得到通知,并更新。

觀察者模式一個比較經典的應用就是:訂閱——發布系統。很容易理解,發布消息時,將消息發送給每個訂閱者。我們常用的微信公眾號就是典型,當我們關注某個公眾號時,每當公眾號推送消息時,我們就會去接收到消息,當然了,每個訂閱(關注)公眾號的的人都能接收到公眾號推送的消息。

那么Android哪里用到了觀察者模式呢?我們看看ListView的適配器,有個函數notifyDataSetChanged()函數,這個函數其實就是通知ListView的每個Item,數據源發生了變化,請各位Item重新刷新一下。

12 備忘錄模式

備忘錄模式定義:在不破壞封閉的前提下,捕獲一個對象的內部狀態,并在對象之外保存這個狀態,這樣,以后就可將對象恢復到原先保存的狀態中。

其實就是相當于一個提前備份,一旦出現啥意外,能夠恢復。像我們平時用的word軟件,意外關閉了,它能幫我們恢復。其實就是它自動幫我們備份過。

那么Android哪里用到了備忘錄模式呢?Activity的onSaveInstanceState和onRestoreInstanceState就是用到了備忘錄模式,分別用于保存和恢復。

13 迭代器模式

迭代器模式定義:提供一種方法順序訪問一個容器對象中的各個元素,而不需要暴露該對象的內部表示。

相信熟悉Java的你肯定知道,Java中就有迭代器Iterator類,本質上說,它就是用迭代器模式。

按照慣例,看看Android中哪里用到了迭代器模式,Android源碼中,最典型的就是Cursor用到了迭代器模式,當我們使用SQLiteDatabase的query方法時,返回的就是Cursor對象,通過如下方式去遍歷:

cursor.moveToFirst();do{//cursor.getXXX(int);}while(cursor.moveToNext);

14 模板方法模式

定義:定義一個操作中的算法框架,而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定的步驟。

不用解釋太多,感覺越解釋越糊涂,直接拿Android中的源碼來說事!

我們知道,啟動一個Activity過程非常復雜,如果讓開發者每次自己去調用啟動Activity過程無疑是一場噩夢。好在啟動Activity大部分代碼時不同的,但是有很多地方需要開發者定制。也就是說,整體算法框架是相同的,但是將一些步驟延遲到子類中,比如Activity的onCreate、onStart等等。這樣子類不用改變整體啟動Activity過程即可重定義某些具體的操作了~。

15 訪問者模式

定義:封裝一些作用于某種數據結構中各元素的操作,它可以在不改變這個數據結構的前提下定義作用于這些元素的新的操作。

訪問者模式是23種設計模式中最復雜的一個,但他的使用率并不高,大部分情況下,我們不需要使用訪問者模式,少數特定的場景才需要。

Android中運用訪問者模式,其實主要是在編譯期注解中,編譯期注解核心原理依賴APT(Annotation Processing Tools),著名的開源庫比如ButterKnife、Dagger、Retrofit都是基于APT。APT的詳細使用這里不提,后面我會寫關于APT相關的文章,敬請期待~

16 中介者模式

定義:中介者模式包裝了一系列對象相互作用的方式,使得這些對象不必相互明顯調用,從而使他們可以輕松耦合。當某些對象之間的作用發生改變時,不會立即影響其他的一些對象之間的作用保證這些作用可以彼此獨立的變化,中介者模式將多對多的相互作用轉為一對多的相互作用。

什么時候用中介者模式呢?其實,中介者對象是將系統從網狀結構轉為以調停者為中心的星型結構。

舉個簡單的例子,一臺電腦包括:CPU、內存、顯卡、IO設備。其實,要啟動一臺計算機,有了CPU和內存就夠了。當然,如果你需要連接顯示器顯示畫面,那就得加顯卡,如果你需要存儲數據,那就要IO設備,但是這并不是最重要的,它們只是分割開來的普通零件而已,我們需要一樣東西把這些零件整合起來,變成一個完整體,這個東西就是主板。主板就是起到中介者的作用,任何兩個模塊之間的通信都會經過主板協調。

那么Android中那些地方用到了中介者模式呢?在Binder機制中,就用到了中介者模式,對Binder不是很熟悉的童鞋請參考我的《 簡單明了,徹底地理解Binder》。我們知道系統啟動時,各種系統服務會向ServiceManager提交注冊,即ServiceManager持有各種系統服務的引用 ,當我們需要獲取系統的Service時,比如ActivityManager、WindowManager等(它們都是Binder),首先是向ServiceManager查詢指定標示符對應的Binder,再由ServiceManager返回Binder的引用。并且客戶端和服務端之間的通信是通過Binder驅動來實現,這里的ServiceManager和Binder驅動就是中介者。

17 代理模式

定義:為其他類提供一種代理以控制這個對象的訪問。

其實代理模式我們平時用的也比較多,其實比較好理解,就是當我們需要對一個對象進行訪問時,我們不直接對這個對象進行訪問,而是訪問這個類的代理類,代理類能幫我們執行我們想要的操作。代理模式比較容易理解,既然你來看這篇文章相信你對代理模式不陌生。

我們直接看看代理模式在Android中的應用,如果你查看AIDL生成的代碼就知道,它會根據當前的線程判斷是否要跨進程訪問,如果不需要跨進程就直接返回實例,如果需要跨進程則返回一個代理,這個代理干什么事情呢?我們在《 簡單明了,徹底地理解Binder》提到,在跨進程通信時,需要把參數寫入到Parcelable對象,然后再執行transact函數,我們要寫的代碼挺多的。AIDL通過生成一個代理類,代理類中自動幫我們寫好這些操作。

18 組合模式

定義:將對象組成成樹形結構,以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。

上面的定義不太好理解,我們直接從Android中用到的組合模式說起。我們知道,Android中View的結構是樹形結構,每個ViewGroup包含一系列的View,而ViewGroup本身又是View。這是Android中非常典型的組合模式。

19 適配器模式

定義:把一個類的接口變換成客戶端所期待的另一個接口,從而使原本因接口不匹配而無法在一起工作的兩個類能夠在一起工作。

其實適配器模式很容易理解,我們在Android開發時也經常用到。比較典型的有ListView和RecyclerView。為什么ListView需要使用適配器呢?主要是,ListView只關心它的每個ItemView,而不關心這個ItemView具體顯示的是什么。而我們的數據源存放的是要顯示的內容,它保存了每一個ItemView要顯示的內容。ListView和數據源之間沒有任何關系,這時候,需要通過適配器,適配器提供getView方法給ListView使用,每次ListView只需提供位置信息給getView函數,然后getView函數根據位置信息向數據源獲取對應的數據,根據數據返回不同的View。

20 裝飾模式

定義:動態的給一個對象添加額外的智者,就增加功能來說,裝飾模式比子類繼承的方式更靈活。

通過簡單代碼來理解裝飾模式:

publicabstractclassComponent{publicabstractvoidoperate();}publicclassConcreteComponentextendsComponent{publicvoidoperate(){//具體的實現}}publicclassDecorator{privateComponent component;publicDecorator(Component component){this.component=component;? ? }publicvoidoperate(){? ? ? ? operateA();? ? ? ? component.operate();? ? ? ? operateB();? ? }publicvoidoperateA(){//具體操作}publicvoidoperateB(){//具體操作}}

那么在Android哪里出現了裝飾模式呢?我們平時經常用到Context類,但是其實Context類只是個抽象類,具體實現是ContextImpl,那么誰是ContextImpl的裝飾類呢?我們知道Activity是個Context,但是Activity 并不是繼承于Context,而是繼承于ContextThremeWrapper.而ContextThremeWrapper繼承于ContextWrapper,ContextWrapper繼承Context.說了這么多,跟裝飾模式有啥關系?主要是引入ContextWrapper這個類。ContextWrapper內部有個Context引用mContext,并且ContextWrapper中對Context的每個方法都有實現,在實現中調用的就是mContext相同的方法。

21 享元模式

定義:使用享元對象有效地支持大量的細粒度對象。

享元模式我們平時接觸真的很多,比如Java中的常量池,線程池等。主要是為了重用對象。

在Android哪里用到了享元模式呢?線程通信中的Message,每次我們獲取Message時調用Message.obtain()其實就是從消息池中取出可重復使用的消息,避免產生大量的Message對象。

22 外觀模式

定義:要求一個子系統的外部與其內部的通信必須通過一個統一的對象進行。

怎么理解呢,舉個例子,我們在啟動計算機時,只需按一下開關鍵,無需關系里面的磁盤、內存、cpu、電源等等這些如何工作,我們只關心他們幫我啟動好了就行。實際上,由于里面的線路太復雜,我們也沒辦法去具體了解內部電路如何工作。主機提供唯一一個接口“開關鍵”給用戶就好。

那么Android哪里使用到了外觀模式呢?依然回到Context,Android內部有很多復雜的功能比如startActivty、sendBroadcast、bindService等等,這些功能內部的實現非常復雜,如果你看了源碼你就能感受得到,但是我們無需關心它內部實現了什么,我們只關心它幫我們啟動Activity,幫我們發送了一條廣播,綁定了Activity等等就夠了。

23 橋接模式

定義:將抽象部分與實現部分分離,使他們獨立地進行變化。

其實就是,一個類存在兩個維度的變化,且這兩個維度都需要進行擴展。

在Android中橋接模式用的很多,舉個例子,對于一個View來說,它有兩個維度的變化,一個是它的描述比如Button、TextView等等他們是View的描述維度上的變化,另一個維度就是將View真正繪制到屏幕上,這跟Display、HardwareLayer和Canvas有關。這兩個維度可以看成是橋接模式的應用。

24 MVC、MVP、MVVP模式

MVC

全稱為Model-View-Controller,也就是模型-視圖-控制器。MVC結構如下圖所示:

MVC

在Android中對MVC的應用很經典,我們的布局文件如main.xml就是對應View層,本地的數據庫數據或者是網絡下載的數據就是對應Model層,而Activity對應Controller層。

MVP

MVP全稱為Model View Presenter,目前MVP在Android應用開發中越來越重要了,它的結構圖如下:

MVP

它降低了View與Model之間的耦合。徹底將View與Model分離。MVP不是一種標準化的模式,它由很多種實現。

MVVM

全稱是Mode View ViewModel,它的結構如下所示:

MVVM

我們在使用ListView時,會自定義一個ViewHolder,在RecyclerView中是必須使用ViewHolder,這主要是提高性能,因為不需要每次去調用findViewById來獲取View。其實ViewHolder就是個ViewModel。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容