編輯:關於Android編程
在Android中使用 MVP 來開發已經出來很久了,剛好Google又出了一系列的architecture samples,在此就整理一下對於MVP的認知和實踐總結,這篇文章會隨著使用經驗的豐富而不斷更新。
在沒有使用MVP開發之前,我們一直使用的都是MVC模式,其實也不算的MVC,一般我們聽到的都是Android中的Activity既是View,又是Controller,即Activity既負責View的顯示,又負責處理業務邏輯,這是我們一般聽到的,但其實我們的Activity還負責了數據的保存和讀取。
當業務邏輯簡單,僅僅只是獲取數據、展示數據時,我們的代碼是這樣:
但其實考慮數據的存儲、業務邏輯等等,我們是這樣:
所有東西都連在一起,太復雜以至於成了一個面了,這個面就是這個God Object:Activity。
想想看,我們是不是把所有東西都寫在這個上帝類中了:網絡請求、解析Json、處理數據、展示數據、緩存數據等等,全部都在Activity中,盡管有些東西已經封裝的很好,但依舊都和Activity扯在一起,基本上所有代碼都在Activity中,於是所謂的MVC,就叫做 massive view controller
,超級大的ViewController。
這樣做盡管有好處,很容易看清楚業務邏輯,而且寫起來很方便、順手,不知不覺就這樣寫了。但是很明顯,隨著需求不斷加多,該類代碼就越寫越多,並且如果需求有改動的話,那改起來很費勁,而且這樣寫,不敢輕易重構,因為太復雜,動一點可能就會有bug。
那麼不想這麼做怎麼辦呢?於是就有了MVP。
典型的MVP就像這樣:
很明顯:
MVP中Presenter和View是相互交互的。 MVP拒絕View和Model直接交互,View和Model只能通過Presenter間接交互。這點是合理的,因為Android對UI的操作必須在 UIThread 上,而對Model的處理大多是異步任務。通過轉接一層,可以很方便處理線程問題。 Model和View是被動的,一切都由Presenter來主導我們一般把 Activity/Fragment 當做View,那麼這時我們的Activity/Fragment中的代碼簡單多了,響應Presenter的指令對View進行操作,而這些操作是在IVIEW
接口中定義好的。這樣大部分代碼轉移到了Presenter中,而且一些業務代碼被封裝到了Model層去,這樣的代碼就會清晰很多,寫完一看,哇塞,太清晰了。而且很容易定位bug,畢竟代碼職責越清晰越不容易出錯。
那麼現在分開來介紹:
Model到這裡,關於MVP的基本概念講解完了,就是分三層寫代碼,此處建議使用MVP時分包時按照模塊分包,模塊比較多時,建一個module包,裝所有模塊,每個模塊再細分三層,這樣包很清晰、代碼也很清晰。
接下來看一些google的示例。
Google samples中的MVP(google MVP示例中這張圖真的很完美的闡述了一個簡單的app的架構):
Model層獲取數據時,使用DataRepository
分發管理,從Presenter處接收指令從RemoteDataSource
或者 LocalDataSource
中獲取數據,並回調給Presenter,Presenter再回調給View用於展示。
下面列出一些官方推薦 的點,這些都可以在代碼中找到:
Use Fragments as View. The separation between Activity and Fragment fits nicely with this implementation of MVP: the Activity is the overall controller that creates and connects views and presenters,which means the Activity is responsible for the creation of fragments and presenters.
使用Fragment作為View,Activity作為全局的Controller把presenter和view綁定起來。
Create a contract
interface defining the view and the presenter,which contains the View interface and the presenter interface. Contracts are interfaces used to define the connection between views and presenters. This is a amazing concept.
創建 IContract
父類接口來管理同一個模塊下的 IView
和 IPresenter
接口,這樣很清楚的能看清楚兩者的邏輯。
In general, the business logic lives in the presenter and relies on the view to do the Android UI work.
業務邏輯全部在Presenter層,依靠View層來觸發。
The view contains almost no logic: it converts the presenter’s commands to UI actions and listens to user actions, which are passed to the presenter.
View幾乎沒有任何邏輯,響應Presenter層的指令,並接收用戶操作傳給Prenster。當然很少很輕量的邏輯應該在這一層中處理(判空處理等),這一點應該要自己衡量。View應該是被動的。
我們應該很容易看到在MVP中僅僅有一些基本原則,並沒有一個固定的方式去實現MVP,都針對自家的業務需求、邏輯去更好的更合適的使用MVP,只要View和Model分離,代碼清晰,易於調試,易於測試,都OK。
MVP的一些指導性原則來約束實現:
Model與View不能直接通信,只能通過Presenter Presenter類似於中間人的角色進行協調和調度 Model和View是接口,Presenter持有的是一個Model接口和一個View接口 Model和View都應該是被動的,一切都由Presenter來主導 Model應該把與業務邏輯層的交互封裝掉,換句話說Presenter和View不應該知道業務邏輯層 View的邏輯應該盡可能的簡單,不應該有狀態。當事件發生時,調用Presenter來處理,並且不傳參數,Presenter處理時再調用View的方法來獲取。知道了MVP是一個方法論,下面說說MVP的另一個實現方式。
MVP另一個實現方式(變種)就是不像一般的把Activity/Fragment作為View,而是把他們作為Presenter來使用,單獨把View用一個類來管理。
使用該種方式可以大大減少類的個數,因為是依靠 泛型 來解耦的,所以可以不用提供接口,當然提供了也行,我在實現過程中刪除了接口,同時把點擊事件的響應結果當成邏輯,放在Presenter中,這樣就不用傳來傳去的。(之前使用Fragment作為View時,點擊ListItem 需要修改已讀狀態,傳入到Presenter中修改,Presenter修改完後又調用View.showDetailUI()方法又傳回到View中,使用變種後這塊就大大減輕工作量了)
使用這種方式個人認為有點怪異,一是使用泛型加大了理解難度,寫著寫著就不知道怎麼調用到這塊了;二是刪除接口後感覺復用性降低了,邏輯也不能復用、View也不能復用,當然可以不刪接口,但是不刪接口和原來的MVP有什麼區別呢。恩,不過這點可能隨著使用MVP越深入會越能理解,Activity到底是View還是控制器,可能隨著項目的偏重不同,會適合不同的情況吧。
那麼說完了MVP變種,下面總結一下個人使用MVP的心得。
使用MVP開發,頓時感覺清爽很多,雖然多了很多代碼,但是瑕不掩瑜,讓代碼清爽很多。真的很棒,看了重構後的代碼,感覺自己之前真是活在噩夢裡一樣,終於不用把所有代碼寫在Activity裡或是Fragment裡了。下面就具體說說使用中的心得體會。
首先是方法論上:
首先分清除什麼是 分發業務邏輯、業務邏輯、視圖邏輯。
理解這三點對於設計接口、使用MVP寫項目、重構等有很大的幫助。視圖邏輯可以更好的寫View,分發業務邏輯用於Presenter,業務邏輯用於Model,當然一些業務邏輯是在Presenter中的。
然後看什麼是View,什麼是邏輯,哪些邏輯展示哪些View,哪些操作調用哪些邏輯,這會很好的幫助我們理清代碼邏輯、方便相互調用。
一個 View 可以有多個 Presenter,要用到什麼業務就加入什麼 Presenter,並且實現這個 Presenter 所需要的 View 接口即可,這就是簡單的復用邏輯,即Presenter是可復用的。
然後是代碼上:
IView
接口裡的方法全部是UI相關,show()、hide()等,不應該出現邏輯相關。 IPresenter
接口裡的方法應該全部是邏輯相關,進行分發,很明顯,要持有Model引用,方便對數據進行操作。不應該出現與Model相關的方法,比如LoadFromLocal,LoadFromRemote,這應該是DataRepository處理的事情。Presenter裡面只需要告訴Model說我需要這個數據,即LoadData,而不用管從哪Load數據。這裡面只需要負責 分發!!! Model
中,對數據進行處理,所有的數據從這裡進出,並對數據進行處理。一般創建DataRepository
類對LocalDataSource
,RemoteDataSource
進行管理分發,這三個類都實現IDataSource
接口,保證邏輯一致。
最後一些個人體會:
在MVP裡面,越來越感到依賴注入的重要性。全部是依賴,全部需要注入,感覺依賴注入框架 Dagger2
是時候展現它強大的功能了。
回調太多。獲取數據時,從Presenter中開始callback,再到DataRepository中,再到具體的LocalDataSource或RemoteDataSource的callback,總共回調了三層,增長了鏈條。很明顯,解決這個問題需要用到 RxJava
了,可以省去很多代碼,變得簡潔是必然的。
Google samples 中的 IContract
接口管理類真的非常棒,管理了IPresenter和IView,如果可以的話,應該讓它也管理IDataSource
這個接口,非常清晰的一種方式。
盡管MVP實現了一定的分離,但相當於大量代碼從Activity中轉移到Presenter和Model中,盡管使用接口使代碼清晰很多,但避免不了難看和難以查找,隨著業務的不斷復雜,代碼依舊會越來越多。所以使用clean架構應該是最好。
最後不得不說確實方法數、類都增加了好多。但這也是必然的。解耦、方便測試。
說了那麼說,show me the code.
那麼,自己寫的代碼全在GitHub上,全部以分支形式展現:
最基礎的分支,全部代碼在Activity中: branch_basic
MVP分支,使用MVP重構代碼:branch_mvp
MVP另一種實現方式(變種)分支:branch_mvp_variant
之後,肯定會嘗試clean架構再次重構。不過由於項目本身較小,所以當個練手的真的不錯。
微信小程序 bnner滾動首先是輪播圖,autoplay 自動播放,interval 輪播的時間,duration 切換速度,可以根據自己需求去添加。Delete:是刪除
這兩天剛剛接觸Unity3d,之前一直是做android開發,對於Unity3d的開發有專門的人才,我主要涉及在Unity3d與android的交互,經過兩天是實驗終於完
Android的屏幕適配一直以來都在折磨著我們這些開發者,本篇文章以Google的官方文檔為基礎,全面而深入的講解了Android屏幕適配的原因、重要概念、解決方案及最
先上效果圖: Title的Layout為: 彈出的dialog的Layout為