細說 Android 的 MVP 模式

稀有猿訴發表於2015-12-14

安卓應用開發是一個看似容易,實則很難的一門苦活兒。上手容易,看幾天Java,看看四大元件咋用,就能整出個不太難看的頁面來。但是想要做好,卻 是很難。系統框架和系統元件封裝了很多東西,開發者弄幾個Activity,用LinearLayout把佈局組合在一起,新增點事件監聽,一個應用就成 型了。紅海競爭,不管多麼複雜的UX和業務邏輯都是一個月快速上線,二週一個迭代,領導和產品早上改需求,晚上改設計,再加上產品經理和設計師都按照 iOS來設計,這一系列原因導致很多安卓應用不但體驗差,不穩定,效能低,而且內部程式碼相當之混亂,即使BAT也是如此。

說說Android的MVP模式

反觀國外市場(谷歌應用市場)上面的大部分應用都還是比較好的,表現在符合安卓設計規範,效能和穩定上表現不俗,體驗上更符合安卓系統,而且會發現他們的程式碼也是很有設計思想的。GitHub上面的很多安卓開源專案也都是源自國外的優秀開發者以及他們的專案。

安卓應用也是軟體,程式碼結構合理,層次清晰不但容易維護而且還容易做自動化測試和單元測試,這是開發者的美好願望,也是提升效率的必然之路。

安卓由於系統架構特性,UI元件Activity中融合了View的處理,事件處理和邏輯處理,隨著業務的越來越複雜,導致Activity也越來越雍腫,幾千行的Activity隨處可見,Fragment也不能解決問題,千行以上的Fragment也不在少數,這個時候就完全不要談什麼可維護性,可測試性了。能完成需求就算高手了。

MVP便應運而生,就來解決這些問題的。

什麼是MVP模式

MVP是針對有GUI存在的應用程式,比如像安卓,像水果以及PC的客戶端軟體中用以劃分組織程式碼的一種設計模式,是由MVC模式升級演進出來的,目的在於,對於GUI層來說,把UI展示與邏輯分開。

  • Model – 為UI層提供的資料,或者儲存UI層傳下來的資料
  • View – 單純的展示資料,響應使用者操作並都轉發給Presenter來做具體的處理
  • Presenter – 邏輯控制層,從Model處取資料,運算和轉化,最後用View來展示;並處理View傳過來的使用者事件,並做處理

說說Android的MVP模式

需要注意的是MVP僅用於應用中的GUI部分,它並不是整個應用的架構方式。一個應用的主要的架構應該包括基礎元件,業務邏輯層和GUI展示層,而MVP僅是用於展示層的設計模式。另外,它是一個方法論的東西,沒有固定的實現方式,只要能體現出它的方法就可以算是MVP。

雖然是方法論,但是也有一些指導性的原則來約束實現:

  • Model與View不能直接通訊,只能通過Presenter
  • Presenter類似於中間人的角色進行協調和排程
  • Model和View是介面,Presenter持有的是一個Model介面和一個View介面
  • Model和View都應該是被動的,一切都由Presenter來主導
  • Model應該把與業務邏輯層的互動封裝掉,換句話說Presenter和View不應該知道業務邏輯層
  • View的邏輯應該儘可能的簡單,不應該有狀態。當事件發生時,呼叫Presenter來處理,並且不傳引數,Presenter處理時再呼叫View的方法來獲取。

從這裡可以看的出來,其實,MVP的目的就是把GUI的邏輯都集中在Presenter層,又把View層和Model與其用介面分離,讓View儘可能的簡單,這樣可以加強移植性。因為View層是肯定不能移植的,不同的平臺GUI的視窗部件肯定不一樣,Model也是不太好移植的,因為每個平臺的IO也都是不一樣的。但是,MVP中的P肯定是可以移植的,因為它裡面只有邏輯,且View和Model都是介面,所以很容易移植。同時,因為View和Model都是介面,這個Presenter也非常好測試,只要實現一個View的介面和Model的介面,就可以單獨的測試Presenter了。

嚴格來講,View只是被動的顯示,提供方法由Presenter來呼叫,資料等都是由Presenter來提供,內部不能任何的邏輯與狀態,邏輯和狀態都應該是在Presenter中。UI事件發生時,呼叫Presenter的方法來處理,不能傳引數,也不能有返回值,在Presenter中處理後再呼叫View來更新資料和狀態。

MVP與MVC的區別

MVC之中邏輯是放在了Model裡,Controller負責橋接View和Model,View發生變化時通知Controller,Controller再通知Model,Model進行邏輯處理,更新資料,然後通知View來重新整理。可以看到MVC中三者之間都有聯絡,如果處理不好,或者當View比較複雜時,三者之間都會雙向關聯。MVC在命令列應用,以及WEB中有大量的應用,但對於客戶端(PC和移動端)的GUI應用,MVC往往解決不了複雜性,移植性上以及可測試性上也沒有優勢。

說說Android的MVP模式

MVP的改進在於:

  • 邏輯放在Presenter中
  • View和Model抽象成為介面

這樣就帶了二個好處:

  • 程式碼更加容易移植
  • 程式碼更加容易加入Unit Testing

如何在安卓中實踐MVP

MVP是一個方法論的東西,也就是沒有任何固定的具體的實現形式,只要能夠把View跟Model解除聯絡,把邏輯都放在Presenter中,那麼就能算得上是MVP,一些具體的實踐的指導性原則:

  • View是一個介面,負責被動的把處理好的資料顯示出來
  • Model也是一個介面,負責獲取資料和儲存資料
  • View呼叫Presenter處理使用者事件也是一個介面,稱為事件Delegate
  • Presenter持有的是View的介面和Model介面

安卓的Activity是一個比較奇葩的角色,在MVP中,既可以用作V,因為一個應用的根佈局總是由Activity來建立的。當然也可以當作P,因為Activity是一個應用的入口,也是出口,再加上一些關鍵的系統事件也都是通過Activity的方法來通知的(比如configChange, saveInstance)。其實,都可以。因為MVP是方法論,並沒有固定的形式,只要是把資料處理的邏輯都封裝在Presenter裡,讓其去控制View和Model,讓Activity來承擔View還是Presenter,其實都可以。

MVP不是銀彈,僅是展示層的一種正規化而已

最重要的一點就是要明白,MVP不會拯救你的應用,不要以為使用了MVP就能讓程式碼更容易維護,更少的Bug,新增新功能會更容易。MVP僅是GUI層的一種程式設計正規化而已,且因為它是方法論的東西,對實現方式並沒有固定的形式,所以會被濫用,如果沒有深刻理解MVP的思想,更加會導致災難性的結果。

軟體,移動應用也不例外,如果功能簡單,業務簡單,那麼程式碼怎麼寫其實也都無大礙,但當功能越來越多,業務越來越複雜的時候,就必須要採取必要的方法來應對複雜度和軟體的可開發性,可維護性。比如,說的誇張一點,一個helloworld式的應用,你怎麼寫都可以。但當功能複雜到一個Activity幾千行程式碼的時候,你再怎麼MVP,MVC或者MVVM都不能解決問題,再怎麼把Activity當成P或者當成V都沒有用。

要知道MVP僅是解決GUI應用程式中展示層的問題,並且它帶來的最大的好處是方便測試和移植,因為邏輯都在P裡面,P持有的又僅是View和Model的介面,所以P是可測試的,Mock一個View的實現,和Mock一個Model的實現,就可以完全脫離平臺和框架的限制來自由的測試P。同樣,移到一個新的框架和平臺後,只需要實現View和Model就可以了,P是不需要改變的。

分層和模組化才是解決應用越來越複雜之道

分層

所謂分層,也就是應用程式的架構方法,把應用程式分成好多層,可以參考Bob大叔的The Clean Architecture

說說Android的MVP模式

至少應該分層三層,最底層是平臺適配層,把用到的平臺的元件,控制元件,工具,比如UI元件,資料庫等等,進行封裝;中間層就是業務層,就是你應用的核心的業務邏輯,或者說你的應用解決了使用者什麼樣子的問題,這一層是不會隨著平臺和UI的改變而改變的。比如新聞閱讀類,那麼從伺服器拉取資料,解析資料,快取資料,為上層提供資料這些事情都屬於業務層;最上面就是展示層或者叫做UI層。展示層是可以呼叫業務層的方法和資料。這樣分層,可以讓展示層只是負責與使用者互動,展示業務資料,展示層會變得簡單很多,同時業務層因為不涉及具體的平臺和UI的細節,就非常容易移植,當移植到新平臺或者要做UI改版也是非常容易做的。

模組化

另外一個就是模組化,其實這是軟體開發的一個非常基本的方法,也是非常有用的一個方法。模組劃分的方法非常簡單就是按照功能來劃分。讓模組處理好自己的事情,暴露統一的介面給外部,定義好輸入與輸出。輸入就以引數和方法形式暴露,輸出最好以Delegate方式,這樣能把耦合降到最低。再由一個統一的頂層類來管理各個模組,頂層直接呼叫各模組,各模組通過Delegate方式來回撥管理者。

對於業務層,模組化相對比較容易,因為這裡並不涉及UI和平臺的特性,業務層都應該是獨立的,可移植的,全都是自己寫的類。

但對於展示層,通常沒有那麼的容易,因為有平臺的限制。比如說安卓,根佈局必須由Activity來建立。首先,模組的劃分也要以功能為界限。然後,就是Activity的佈局,要把佈局按功能區域來管理,然後把每個功能模組的top container傳給模組,具體內部如何佈局,如何填充資料,就由模組自己負責。Activity就起管理各個模組的作用。再有,模組間的通訊,可以都通過Activity來,比如模組1有模組2的入口按扭,但是模組1與模組2之間沒有交集,這個時候的處理方式就是模組1Delegate給Activity,然後Activity再呼叫模組2來顯示和隱藏。如果模組多到Activity的管理工作也變得龐大複雜時就要拆出子Controller來管理模組,也就是三層,甚至還可以四層。模組的原則就是做好封裝,讓外層管理變得簡單,這樣外層管理的複雜度就會降下來,就好比公司人員的組織架構一樣。

說說Android的MVP模式

說說Android的MVP模式

<LinearLayout>
  <LinearLayout id="module1" />
  <RelativeLayout id="module2" />
  <ListView id="module3" />
</LinearLayout>
public class DemoActivity extends Activity implements Module1Delegate, Module2Delegate {
  @Override
  public void onCreate(Bundle bundle) {
    setContentView(R.layout.demo_activity);
    Module1 module1 = new Module1(findViewById(R.id.module1), this);
    Module2 module2 = new Module2(findViewById(R.id.module2), this);
    Module3 module3 = new Module3(findViewById(R.id.module3));
    module1.render();
    module2.render();
  }

  @Override
  public void onModule1() {
    Log.e("Demo", "module1 say hello to the world.");
  }

  @Override
  public void onModule2(boolean show) {
    if (show) {
      module3.show();
    } else {
      module3.hide();
    }
  }

其實,還可以做的更徹底一些,那就是Activity中的佈局都由ViewStub來組裝,然後由各個子模組來決定如何佈局。

對於多層全屏層疊的應用來說,要簡單一些,對於每一層都可以由Activity或者Fragment來實現,如果業務層已經抽離出來,就都可以直接呼叫業務層來獲取資料,因此也不會有傳遞資料的麻煩。

做好了分層和模組化,我相信,能解決絕大多數應用遇到的問題。至於模組內部用什麼MVP,MVC,MVVM,其實真的無大害,因為模組內部的實現方式不影響其他模組,也不影響外部管理和level更高的類。

把基本的原則做到就夠了

程式設計是一項社會活動,所以人和人與人之間的關係才是核心,優秀的人,你發現他也沒有用什麼MVP,什麼MVC,什麼高大上的設計模式和演算法,但是他的程式碼是很清晰,很容易看懂。有些即使號稱用什麼高大上的,最先進的設計模式,但是程式碼仍是一坨坨的,可能連他自己都看不懂。

把基本的抽象和封裝真正做到位了,就夠了,程式碼水平可以的話,再能做到命名見名知義,小而活的方法,小而活的類,一個方法只做一件事,一個類只做一件事情。做到這些,也就夠了。

至於什麼高大上的MVP,什麼XP,什麼TDD,什麼結對,其實都是浮雲,如果你的水平比較高,程式碼sense較高,那麼用不用這些方法差別不大。

MVP的核心目的是方便UT,因為把展示層的邏輯都集中在P,而P又不依賴於具體的View和Model,所以可以隨便Mock一個View和一個 Model來測試P,甚至P可以獨立於平臺的限制來單獨的測試。所以,如果你不搞UT,以不以MVP方式來實現,其實沒啥影響,甚至網上不少人還專門為 MVP而弄出幾個抽象的類,把Activity啥的封裝了一下,號稱MVP框架,毫無實用價值。軟體方法,切忌生搬硬套,一定要先理解透徹方法,再理解透徹你的問題和環境限制,然後靈活運動,什麼叫理解透徹呢?就是你能給別人講明白時。這說起來還是太抽象,只能在實際運用中慢慢領悟。

再有就是Unit Testing這 玩意兒,實際的意義也沒有那麼大,要知道寫測試程式碼通常要比生產程式碼花更多的精力,前提還是你的程式碼寫的可測,可測性比可讀性還要難一點,說白了這對開發 者水平的要求相當的高,不是看了一遍書,學習一下JUnit就能搞得好的。還有就是如果你的需求經常變動,移動互聯時代這是家常便飯,那麼做UT會讓開發 量double甚至tripple,因為之前寫的UT全沒有用。

還想說一點就是,軟體開發方法這東西必須是由上向下推動,也就是由老闆帶頭來推動,否則技術小組長或者開發者自己是很難推得動的。特別是像 UT,Code Review或者結對之類的會“降低開發效率”的方法。這些方法短期內不會提升效率和質量,只會降低需求的產出率,平均開發水平比較高的團隊也至少要幾個 月後才能真正的適應這些方法,然後才有可能提高效率和提高質量。如果不是老闆主動推動,誰能受得了呢?KPI咋整?

結論

MVP或者MVVM帶來最大的好處是:

  • 方便移植
  • 方便UT

另外,要注意MVP僅是展示層的方法論。應用整體還是要進行分層和模組化。如果分層和模組化進行的徹底,並且在移植和UT沒有強烈的需求,其實MVP與不P真的不重要。

參考資源

相關文章