MVP簡(jiǎn)介
最近幾天在啃MVP,現(xiàn)在的你或許跟幾天前的我一樣,對(duì)MVP還是一臉懵逼,雖然MVP三個(gè)字母都認(rèn)識(shí),但連在一起卻不明白到底是個(gè)什么東東,沒(méi)關(guān)系,快來(lái)干了這碗雞湯,立馬從懵逼到入門(mén),入不了門(mén)你來(lái)打我,文末統(tǒng)計(jì)人數(shù)。
首先,MVP是一種設(shè)計(jì)模式,或者說(shuō)架構(gòu)。Google把它列入Android Architecture Blueprints--Android 架構(gòu)藍(lán)圖,并給出了官方的例子來(lái)解釋如何實(shí)現(xiàn)MVP。本文實(shí)現(xiàn)的登陸功能就是根據(jù)MVP基礎(chǔ)架構(gòu)Demo來(lái)實(shí)現(xiàn)的。
其次,MVP從何而來(lái)?想必都知道是MVC的演化版本,現(xiàn)在比較流行,被廣大開(kāi)發(fā)者所認(rèn)可。被認(rèn)可的原因我在這里總結(jié)一下:
- 代碼清晰,容易理解(掌握MVP的前提下)
- 簡(jiǎn)化了萬(wàn)能的Activity的邏輯
- 解耦了View和Model
- 方便單元測(cè)試
一直以來(lái),在MVC模式中Activity的萬(wàn)能角色就備受詬病,一旦邏輯越來(lái)越復(fù)雜,Activity就越來(lái)越臃腫,承擔(dān)越來(lái)越多的職責(zé),代碼閱讀起來(lái)費(fèi)勁,維護(hù)成本跟著提高,而且,在MVC模式中,View 和 Model直接交互,耦合度高,違背了軟件開(kāi)發(fā)“高類聚、低耦合”的設(shè)計(jì)目標(biāo)--于是MVP橫空出世。
關(guān)于MVP的理論知識(shí)就嗶嗶這些,想要詳細(xì)了解的童鞋回頭自己做功課去,這里不是重點(diǎn),本文的重點(diǎn)是把MVP模式實(shí)踐起來(lái)。
MVP實(shí)現(xiàn)登陸

這是google samples TODO-MVP項(xiàng)目中的MVP圖解。因?yàn)楸救藢W(xué)習(xí)MVP主要也是根據(jù)谷歌官方demo來(lái)的,所以,這個(gè)圖直接拿來(lái)。這個(gè)圖怎么看?
分為左右兩部分,左邊沒(méi)顏色的部分包含的內(nèi)容是Model,這里面包含了數(shù)據(jù)實(shí)體模型、數(shù)據(jù)訪問(wèn)接口、SQLite數(shù)據(jù)庫(kù)操作、網(wǎng)路數(shù)據(jù)操作、數(shù)據(jù)內(nèi)存緩存,這些都是Model要做的事情,跟MVC沒(méi)什么差別;
右邊...那個(gè)什么顏色的背景(色盲晚期),注意那個(gè)Activity,我們看到VIEW和PRESENTER都被放在了Activity里邊,而VIEW的實(shí)現(xiàn)類在這里用的是Fragment;
-
隱藏內(nèi)容:這里面其實(shí)還有一個(gè)內(nèi)容--契約類,也就是項(xiàng)目里的XXXContract.java類,是一個(gè)接口類,作用是定義VIEW接口和PRESENTER接口提供的接口方法。這個(gè)類原則上不屬于MVP模式里的任何角色,所以沒(méi)在上圖出現(xiàn),可以理解。
先看看效果 不方便看視頻的就看看圖吧!
[圖片上傳失敗...(image-32673-1521790364530)]
[圖片上傳失敗...(image-17e711-1521790364530)]
-
1.創(chuàng)建View接口和Presenter接口基類
BaseView作為View接口基類,定義了一個(gè)重要的接口:void setPresenter(T presenter);
[圖片上傳失敗...(image-220445-1521790364530)]
[圖片上傳失敗...(image-edaa6-1521790364530)]
這其實(shí)是MVP的一個(gè)關(guān)鍵點(diǎn),通過(guò)這個(gè)接口,View的實(shí)現(xiàn)類(即Fragment)就持有了Presenter的實(shí)例,于是,View就可以通過(guò)Presenter來(lái)操作Model中的數(shù)據(jù)接口了。如果你要實(shí)現(xiàn)MVP模式,記住,不管三七二十一,先寫(xiě)這個(gè)基佬,哦,不對(duì),是基類。
Presenter接口基類里同樣定義了一個(gè)接口:
void start();
這個(gè)方法就是直接操作Model的,比如加載數(shù)據(jù)。通過(guò)這兩個(gè)基類的定義的接口就能看出,View和Model不直接交互,而是通過(guò)Presenter來(lái)操作,這是與MVC的不同之處。
-
2.登陸契約類LoginContract
public interface LoginContract {interface Presenter extends BasePresenter { void login(); void reset(); } interface View extends BaseView<Presenter> { String getUserEmail(); String getPassword(); boolean isEmailValid(String email); boolean isPasswordValid(String password); boolean setEmailError(String error); boolean setPasswordError(String error); void showLoginProgress(boolean show); void resetEditView(); void toMainAct(); void showFailedError(); } }這個(gè)類是首次出現(xiàn)于google的mvp示例中,以前的MVP模式并未見(jiàn)到,這個(gè)類定義了View接口和Presenter接口為對(duì)方的實(shí)例提供的方法。
比如,我在View中可以獲取用戶輸入的郵箱和密碼,判斷郵箱密碼是否有效,設(shè)置郵箱密碼輸入框錯(cuò)誤提示信息,顯示登陸ProgressBar等,同樣,在Presenter接口中,提供了登陸和重置兩個(gè)功能,用戶通過(guò)View上的兩個(gè)按鈕,響應(yīng)Presenter對(duì)應(yīng)的接口,執(zhí)行相關(guān)的業(yè)務(wù)邏輯。
這個(gè)契約類的好處是方便接口統(tǒng)一管理、修改,同時(shí),內(nèi)容清晰,一目了然。
-
3.View的實(shí)現(xiàn)類LoginFragment implements LoginContract.View
實(shí)現(xiàn)接口定義的各個(gè)方法,必須持有Presenter,并通過(guò)接口void setPresenter(T presenter)
為其賦值。
注意官方的demo說(shuō)明里有這段內(nèi)容:
Note: in a MVP context, the term "view" is overloaded:
The class android.view.View will be referred to as "Android View"
The view that receives commands from a presenter in MVP, will be simply called "view".
我來(lái)獻(xiàn)個(gè)丑,翻譯一下:
注意:在MVP的上下文里,“view”一詞有多重含義:
- android.view.View被稱為“Android View”
- 在MVP中,從presenter接收命令的view將被簡(jiǎn)單地稱為“view”。
什么意思?
我的理解是這樣的:MVP中的VIEW由兩部分組成,一個(gè)是view接口,比如上面的LoginContract.View接口;一個(gè)是該接口的實(shí)現(xiàn)類,比如上面的LoginFragment。view接口負(fù)責(zé)與presenter交互,presenter調(diào)用view接口定義方法來(lái)操作view的實(shí)現(xiàn)類;具體實(shí)現(xiàn)都是在android.view.View里實(shí)現(xiàn)的,即LoginFragment。
-
4.Presenter的實(shí)現(xiàn)類LoginPresenter implements LoginContract.Presenter
實(shí)現(xiàn)接口定義的各個(gè)方法,必須持有Model對(duì)象和View對(duì)象private final UserRepository mUserRepository; private final LoginContract.View mLoginView;
然后,你想讓View干嘛,調(diào)用View相對(duì)應(yīng)的接口就行了,想要什么數(shù)據(jù),想對(duì)數(shù)據(jù)做什么操作,調(diào)用Model對(duì)象的對(duì)應(yīng)方法就行了;或許你已經(jīng)發(fā)現(xiàn)了:
Presenter對(duì)View的操作都是通過(guò)接口來(lái)完成的。
- 5.Activity的角色,看Google官方Sample里怎么介紹的:
It uses fragments for two reasons:
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.
Tablet layout or screens with multiple views take advantage of the Fragments framework.
獻(xiàn)丑二進(jìn)宮:
(MVP中的View實(shí)現(xiàn))使用Fragment有兩個(gè)原因:
Activity與Fragment之間的分離很好的符合了MVP的實(shí)現(xiàn):Activity作為整體控制器來(lái)創(chuàng)建和連接views與presenters。
Tablet布局或者屏幕上有多個(gè)views的布局可以很好的利用Fragments框架。
在MVP模式里,Activity的功能變得簡(jiǎn)單了很多,一是創(chuàng)建View布局(Fragment),二是實(shí)例化Presenter(LoginPresenter),并將View(Fragment)作為參數(shù),傳入到Presenter(LoginPresenter)中,在Presenter(LoginPresenter)構(gòu)造函數(shù)中傳遞給Presenter(LoginPresenter)持有的View對(duì)象,然后View對(duì)象調(diào)用setPresenter方法,將自身this傳遞給View實(shí)例(Fragment)。也就是上面說(shuō)的“創(chuàng)建和連接views與presenters。”
來(lái)看看Activity代碼多簡(jiǎn)單:
public class LoginActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
LoginFragment loginFragment = (LoginFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
// Create the view
if (loginFragment == null) {
loginFragment = LoginFragment.newInstance("LOGIN_FRAGMENT");
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.contentFrame, loginFragment);
transaction.commit();
// Create the presenter
new LoginPresenter(
getApplicationContext(),
UserRepository.getInstance(
UserLocalDataSource.getInstance(getApplicationContext()),
UserRemoteDataSource.getInstance()),
loginFragment);
}
}
至此,MVP模式里的VP就可以運(yùn)行起來(lái)了,連通起來(lái)了。下面來(lái)說(shuō)說(shuō)Model。如果對(duì)MVC的Model非常熟悉可以跳過(guò)。
-
6.Model的創(chuàng)建
在目錄結(jié)構(gòu)圖中,整個(gè)data package里的都是Model的內(nèi)容,包括實(shí)體模型(User類)、本地?cái)?shù)據(jù)庫(kù)操作(local package)、遠(yuǎn)程數(shù)據(jù)訪問(wèn)(remote package)三部分,跟在MVC里并無(wú)差別,這里不展開(kāi)介紹。業(yè)務(wù)邏輯需要什么樣的數(shù)據(jù)實(shí)體、數(shù)據(jù)操作,在對(duì)應(yīng)的包里面構(gòu)建就行了,這里要提到的是,Presenter對(duì)Model的持有,這里也是通過(guò)接口實(shí)現(xiàn)的,間接通過(guò)UserDataSource接口類,直接通過(guò)UserDataSource的實(shí)現(xiàn)類UserRepository。而UserRepository同時(shí)持有對(duì)本地?cái)?shù)據(jù)和遠(yuǎn)程數(shù)據(jù)操作的對(duì)象:
private final UserDataSource mLocalDataSource;
private final UserDataSource mRemoteDataSource;
MVP實(shí)踐總結(jié)
使用MVP模式也有一些不盡如人意的地方,比如,類和接口變多了,代碼也多了,項(xiàng)目大了可能會(huì)不好管理,但這都不是事,用多點(diǎn)代碼、多點(diǎn)類文件換取低耦合度、結(jié)構(gòu)清晰、容易理解、易擴(kuò)展的架構(gòu),這買賣值了。
第一遍看的時(shí)候懵逼沒(méi)關(guān)系,再看一遍,認(rèn)真的解讀MVP的設(shè)計(jì)思路,參考代碼,發(fā)現(xiàn)其實(shí)真的是很清晰的思路,并不難,難的是啃下來(lái)的決心。這碗雞湯我干了,你們隨意。
相信看完這篇文章會(huì)對(duì)你理解MVP有所幫助,如果你還是一臉懵逼的話,請(qǐng)舉起手來(lái):
[圖片上傳失敗...(image-e459b1-1521790364530)]
最后,奉上本文的源碼GitHub:Login-MVP-Architecture,如果覺(jué)得有用,點(diǎn)個(gè)Star表示支持。