MVP模式是你的救命稻草嗎?

為什么要學(xué)習(xí)架構(gòu)?

不管是MVC還是MVP,亦或則其他架構(gòu),它們的設(shè)計(jì)目的都是為了達(dá)到編碼的最高境界,那就是:低藕合,高復(fù)用,易測(cè)試,好維護(hù)。

而要達(dá)到這個(gè)終極目標(biāo),首先要理解的是每個(gè)部分各自負(fù)責(zé)些什么,以及如何組合在一起。因此我個(gè)人認(rèn)為,學(xué)習(xí)架構(gòu)關(guān)鍵在兩步:

  1. 如何把纏在一起的代碼拆分。
  2. 如何把拆開的代碼再組合

很多新手在剛做項(xiàng)目時(shí),都會(huì)把所有的代碼,如數(shù)據(jù)的訪問(wèn)和處理,數(shù)據(jù)的展示,用戶的輸入都寫在一起,代碼和思維都呈現(xiàn)出一種線性的形式,一切都是直來(lái)直往的。這樣代碼量確實(shí)少,寫的時(shí)候也覺(jué)得方便,但是帶來(lái)了幾個(gè)致命的問(wèn)題:

  1. 當(dāng)業(yè)務(wù)越來(lái)越復(fù)雜時(shí),修改代碼成了噩夢(mèng)。同樣的邏輯放在不同的地方,本來(lái)只用改一處的變成了需要改幾百處。而又因?yàn)樗械倪壿嫸蓟ハ酄砍蹲?,?dǎo)致本來(lái)只想改一處的,結(jié)果卻影響了幾百處。
  2. 當(dāng)時(shí)間越來(lái)越遙遠(yuǎn)時(shí),理解代碼成了噩夢(mèng)。本來(lái)只需要閱讀幾行的時(shí)候,卻因?yàn)樗械拇a都雜在一起變成了要閱讀幾千行。時(shí)間一長(zhǎng),重新閱讀時(shí),別說(shuō)別人,就是自己也很難一下就能掌握關(guān)鍵點(diǎn)。
  3. 當(dāng)需要做一個(gè)功能時(shí),卻發(fā)現(xiàn)代碼無(wú)法復(fù)用,明明是一樣的邏輯也只能靠Ctrl+CCtrl+V。這又為今后修改代碼時(shí)增加了工作量。
  4. 當(dāng)需要測(cè)試時(shí)確發(fā)現(xiàn)很難進(jìn)行測(cè)試,每次修改一處代碼后都必須進(jìn)行重復(fù)的人工測(cè)試,無(wú)法進(jìn)行自動(dòng)化測(cè)試,模塊和模塊也無(wú)法拆開來(lái)進(jìn)行獨(dú)立的單元測(cè)試,每次都要整體的測(cè)一遍才能確保一切完好如初。

要換的不是架構(gòu),而是思維方式

其實(shí)目前市面上的架構(gòu)模式已經(jīng)有很多種,各有不同,但模式終究只是一種設(shè)計(jì)理念的表現(xiàn)形式,學(xué)習(xí)再多的架構(gòu),你也只是多會(huì)用了幾種工具而已,學(xué)習(xí)一種模式其實(shí)是在學(xué)一種思維方式:

如何在解決問(wèn)題的時(shí)候把問(wèn)題合理的拆分,又如何將拆分的零件合理的組裝成解決問(wèn)題的工具。

將這種思維方式深入到你的大腦里,血液里,直至骨髓,讓它成為你思考問(wèn)題的一種習(xí)慣和定式,逐漸成為你的一部分,那么這時(shí)你已達(dá)到無(wú)招勝有招的境界了。

閑話先不扯了,回正題。

實(shí)現(xiàn)架構(gòu)沒(méi)有唯一標(biāo)準(zhǔn)

這里首先需要說(shuō)明的是無(wú)論MVP或MVC還是MVVM等任何一種架構(gòu)和模式其實(shí)都沒(méi)有誰(shuí)優(yōu)誰(shuí)劣之分,而且就算是同一種架構(gòu),也可以根據(jù)不同的使用場(chǎng)景來(lái)做不同的實(shí)現(xiàn)方式,這里并沒(méi)有宇宙絕對(duì)的對(duì)錯(cuò)標(biāo)準(zhǔn)和概念定義。這和張三豐在教無(wú)忌太極拳以后讓其先忘掉招式是一樣的道理,在應(yīng)用型領(lǐng)域,定式和概念只應(yīng)是在學(xué)習(xí)的過(guò)程中才存在的,而真正學(xué)會(huì)以后應(yīng)該馬上忘掉定式,忘掉概念,將其用熟用活才是關(guān)鍵。

所以說(shuō)我在此描述的概念也不是唯一的標(biāo)準(zhǔn),而只是個(gè)人對(duì)其的理解。

什么是MVC

因?yàn)镸VP是MVC的一個(gè)變種,因此我們先來(lái)看在MVC里代碼是如何拆分和組合的,這里簡(jiǎn)要的描述常見(jiàn)的定義:

  1. 拆分:Model負(fù)責(zé)數(shù)據(jù),View負(fù)責(zé)顯示,Controller負(fù)責(zé)用戶輸入。
  2. 組合:View將用戶操作和事件傳遞給Controller,Controller將用戶命令翻譯成消息來(lái)傳遞給Model,Model在處理完數(shù)據(jù)后,通知View來(lái)顯示給用戶,而View又從Model取出數(shù)據(jù)。

我認(rèn)為在學(xué)習(xí)MVP之前,必須深刻理解什么叫做MVC,因?yàn)閮烧叩膮^(qū)別其實(shí)沒(méi)有你想象中的那么大,與其神話并盲目追崇新的架構(gòu),期望其能解脫你于苦海,還不如深刻的理解MVC的設(shè)計(jì)理念,這就和學(xué)習(xí)一門新語(yǔ)言一樣,只有你真正深刻的理解了其思維方式,那么學(xué)習(xí)新的一門語(yǔ)言其實(shí)都是易如反掌的。因?yàn)樗鼈兤鋵?shí)都是在做一件事,只是做的過(guò)程上有些許差異而已。

更多MVC的理解,可以參考我之前寫的一篇文章:在談MVP之前,你真的懂MVC嗎?

什么是MVP

MVP與MVC最大的區(qū)別就在與將Model和View通過(guò)Presenter隔開了,不再允許其互相直接通信,而所有的消息都是通過(guò)Presenter這個(gè)中間人來(lái)傳遞。

而這樣做的目的主要是為了將數(shù)據(jù)和展示劃出更明確的界限。

首先我們來(lái)看MVP到底是在解決什么問(wèn)題的:

  • 數(shù)據(jù)管理
    1. 什么是數(shù)據(jù)?(Model)
    2. 如何篩選數(shù)據(jù)?(Selections)
    3. 如何改變數(shù)據(jù)?(Commands)
  • 用戶界面
    1. 如何展示數(shù)據(jù)?(View)
    2. 如何通過(guò)事件來(lái)改變數(shù)據(jù)?(Interactor)
    3. 如何將這些放在一起?(Presenter)

可以看出其實(shí)一個(gè)GUI程序的核心,還是在于用戶和數(shù)據(jù)之間的關(guān)系。而架構(gòu)的引入也是為了解決這幾個(gè)核心的問(wèn)題。

那么MVP又是如何解決這幾個(gè)問(wèn)題的,Model,View,Presenter三者又各自負(fù)責(zé)什么呢?誰(shuí)來(lái)負(fù)責(zé)請(qǐng)求網(wǎng)絡(luò)數(shù)據(jù),訪問(wèn)和存儲(chǔ)本地?cái)?shù)據(jù),誰(shuí)來(lái)負(fù)責(zé)處理數(shù)據(jù),誰(shuí)來(lái)負(fù)責(zé)顯示數(shù)據(jù),誰(shuí)又來(lái)負(fù)責(zé)和用戶交互呢?

具體表現(xiàn)在代碼上,也可以說(shuō):網(wǎng)絡(luò)請(qǐng)求應(yīng)該放在哪,數(shù)據(jù)庫(kù)訪問(wèn)應(yīng)該放在哪,業(yè)務(wù)邏輯應(yīng)該放在哪里,處理用戶輸入應(yīng)該放在哪,誰(shuí)又來(lái)控制顯示或隱藏View的等具體的細(xì)節(jié)問(wèn)題。

帶著這些具體問(wèn)題,我們一起來(lái)學(xué)習(xí)。

如何拆分

首先來(lái)看MVP各自負(fù)責(zé)什么:

  1. Model,負(fù)責(zé)定義數(shù)據(jù)(解決什么是數(shù)據(jù))
  2. Presenter, 負(fù)責(zé)在Model和View之間,從model里取出數(shù)據(jù),格式化后在View上展示(解決如何把數(shù)據(jù)和用戶界面放在一起)。
  3. View,負(fù)責(zé)擔(dān)任一個(gè)被動(dòng)界面,用于展示數(shù)據(jù)。(解決如何展示數(shù)據(jù))

和MVC比較而已,這里出現(xiàn)一個(gè)最大的疑問(wèn)就是:那么誰(shuí)來(lái)負(fù)責(zé)和用戶的操作交互呢?答案是,用戶在View上的所有操作(事件)都由View路由給Presenter,由Presenter來(lái)與其交互。

而和MVC最大的區(qū)別在于Model和View完全被Presenter隔開了,Presenter作為它們之間的中間人去傳遞所有數(shù)據(jù)。

如何組合

三者又是如何組合起來(lái)的呢?

很顯然Presenter作為中間者,它是同時(shí)擁有View和Model的引用的,為了在它們之間起到橋梁作用,即Presenter會(huì)主動(dòng)和View和Model進(jìn)行通信。

Model和View必須是完全隔離的,不允許兩者之間互相通信,保持對(duì)彼此的不感知,這樣的好處是你徹底將數(shù)據(jù)和展示分離來(lái)開,并且可以獨(dú)立的為Model去做測(cè)試。

Model在三者中是獨(dú)立性最高的,Model不應(yīng)該擁有對(duì)View的引用,而且Model也不需要保存對(duì)Presenter的引用,對(duì)于Presenter而已,Model只需要提供接口,等著Presenter來(lái)調(diào)用時(shí)返回相應(yīng)數(shù)據(jù)即可,這和經(jīng)典MVC模式中是非常不同的,在MVC中Model在數(shù)據(jù)發(fā)送變化后,是需要發(fā)送廣播來(lái)告之View去更新用戶界面的,而在MVP中,Model是不應(yīng)該去通知View,而是通知Presenter來(lái)間接的更新View的。

Presenter和Model的關(guān)系也應(yīng)該是基于接口來(lái)通信,這樣才能把Model和Presenter的耦合度也降到最低,那么在需要改變Model內(nèi)部實(shí)現(xiàn),甚至徹底替換Model的時(shí)候,Presenter則是無(wú)需隨之改變的。這樣做帶來(lái)的另一個(gè)好處就是你可以通過(guò)Mock一個(gè)Model來(lái)對(duì)Presenter以及View做模擬測(cè)試了,從而提高了可測(cè)試性。

那么View和Presenter的關(guān)系呢?View是需要擁有對(duì)Presenter的引用,但僅僅是為了將用戶的操作和事件立即傳遞給Presenter,為了讓View和Presenter耦合較低,View也只應(yīng)該通過(guò)接口與Presenter通信,從而保證View是完全被動(dòng)的,一方面它由用戶的操作觸發(fā)來(lái)和Presenter通信,另一方面它完全受Presenter控制,唯一需要做的事情就是如何展示數(shù)據(jù)。

簡(jiǎn)要總結(jié)三者之間的關(guān)系是:View和Model之間沒(méi)有聯(lián)系,View通過(guò)接口向Presenter來(lái)傳遞用戶操作,Model不主動(dòng)和Presenter聯(lián)系,被動(dòng)的等著Presenter來(lái)調(diào)用其接口,Presenter通過(guò)接口和View/Model來(lái)聯(lián)系。

View <- 接口 <- Presenter ->接口 -> Model

View -> 接口 -> Presenter <- 接口 <- Model

Talk is cheap, show me the code

為了便于理解,這里提供一些偽代碼加注釋演示一下如何實(shí)現(xiàn)MVP模式:

View

interface IUserView {
  
  void setPresenter(presenter);
  void showUsers(users);
  void showDeleteUserComplete();
  void showDeleteUserError();
  
}

class UserView implements IUserView {

  UserPresenter presenter;
  
  // 保持對(duì)Presenter的引用,用于路由用戶操作
  void setPresenter(presenter) {
      this.presenter = presenter;
  }
  
  // 將Presenter傳遞來(lái)的數(shù)據(jù)展示出來(lái)
  void showUsers(users) {
      draw(users);
  }
  
  // Model操作數(shù)據(jù)成功后,通過(guò)Presenter來(lái)告之View需要更新用戶界面
  void showDeleteUserComplete() {
      alert("Delete User Complete");
  }
  
  // Model操作數(shù)據(jù)失敗后,也是通過(guò)Presenter來(lái)告之View需要更新用戶界面
  void showDeleteUserError() {
      alert("Delete User Fail");
  }
  
  // 當(dāng)用戶點(diǎn)擊某個(gè)按鈕時(shí),將用戶操作路由給presenter,由presenter去處理
  void onDeleteButtonClicked(event) {
      presenter.deleteUser(event);
  }
  
}

Model

interface IUserModel {
  
  List<User> getUsers();
  boolean deleteUserById();

}

class UserModel implements IUserModel {
  
  // 在數(shù)據(jù)庫(kù)里查找數(shù)據(jù),并將數(shù)據(jù)返回給presenter
  List<User> getUsers() {
    return getUsersInDatabase(id);
  }
  
  // 在數(shù)據(jù)庫(kù)里刪除數(shù)據(jù),并將結(jié)果返回給presenter
  User deleteUserById(id) {
    return deleteUserByIdInDatabase(id);
  }
  
}

Presenter

interface IUserUserPresenter {
  
  void deleteUser(event);

}

class UserUserPresenter implements IUserPresenter {
  
  // 保持對(duì)View的引用
  IUserView view;
  // 保持對(duì)Model的引用
  IUserModel model;
  
  UserUserPresenter(IUserView view, IUserModel model) {
    this.view = view;    
    this.model = model;
    
    this.view.setPresenter(this);   
  }
  
  void start() {
    // 從Model中取出數(shù)據(jù)
    List<User> users = model.getUsers();
    // 將數(shù)據(jù)發(fā)送給View,讓其展示到用戶界面
    view.showUsers(users);
  }
  
  void deleteUser(event) {
    // View將用戶操作路由過(guò)來(lái),由Presenter來(lái)處理
    long uid = whichUserNeedToDeleteBy(event);
    // 將用戶操作翻譯成命令或消息傳遞給model,以改變數(shù)據(jù)
    boolean success = model.deleteUserById(uid);
    // 將Model操作數(shù)據(jù)后的結(jié)果通知View來(lái)改變用戶界面
    if (success) {
        view.onDeleteUserSuccess();
    } else {
        view.onDeleteUserFail();  
    }
  }
}

OK,到此一個(gè)最簡(jiǎn)單的MVP模式就實(shí)現(xiàn)了,可以看到整個(gè)架構(gòu)的輪廓已經(jīng)很清晰了,而且面向接口編程也帶來(lái)了很多的好處,讓代碼之間藕和較少,對(duì)測(cè)試支持也更為友好,理解和維護(hù)起來(lái)也更加方便了。

以后有時(shí)間再為大家描述如何在android里面實(shí)現(xiàn)MVP模式,以便更具體的理解。有疑問(wèn)歡迎參與討論,大家一起學(xué)習(xí)進(jìn)步。

參考文檔

  1. Model–view–presenter WIKI
  2. MVP: Model-View-PresenterThe Taligent Programming Model for C++ and Java
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,382評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,616評(píng)論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,112評(píng)論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,355評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,869評(píng)論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,727評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,928評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,165評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評(píng)論 1 282
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,585評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,892評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,618評(píng)論 25 708
  • 前言 看了下上篇博客的發(fā)表時(shí)間到這篇博客,竟然過(guò)了11個(gè)月,罪過(guò),罪過(guò)。這一年時(shí)間也是夠折騰的,年初離職跳槽到鵝廠...
    西木柚子閱讀 21,274評(píng)論 12 184
  • 作者:李旺成 時(shí)間:2016年4月3日 “Android MVP 詳解(下)”已經(jīng)發(fā)布,歡迎大家提建議。 MVP ...
    diygreen閱讀 128,928評(píng)論 86 1,321
  • 沒(méi)有督促的壓力好像變成了混日子,少了主動(dòng),也喪失了方向,更重要的是感覺(jué)到了無(wú)用。如果做的事情沒(méi)了價(jià)值,或者沒(méi)了可以...
    聶一一閱讀 123評(píng)論 0 0
  • 正常情況下,在使用CoreGraphics框架中的方法進(jìn)行圖形繪制時(shí),每一閉合的圖形都是一個(gè)獨(dú)立的層,如果在繪制時(shí)...
    F麥子閱讀 359評(píng)論 0 0