mvp模式

爲什麼要學習架構?

不管是MVC還是MVP,亦或則其他架構,它們的設計目的都是爲了達到編碼的最高境界,那就是:低藕合,高複用,易測試,好維護。

而要達到這個終極目標,首先要理解的是每個部分各自負責些什麼,以及如何組合在一起。因此我個人認爲,學習架構關鍵在兩步:

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

很多新手在剛做項目時,都會把所有的代碼,如數據的訪問和處理,數據的展示,用戶的輸入都寫在一起,代碼和思維都呈現出一種線性的形式,一切都是直來直往的。這樣代碼量確實少,寫的時候也覺得方便,但是帶來了幾個致命的問題:

  1. 當業務越來越複雜時,修改代碼成了噩夢。同樣的邏輯放在不同的地方,本來只用改一處的變成了需要改幾百處。而又因爲所有的邏輯都互相牽扯住,導致本來只想改一處的,結果卻影響了幾百處。
  2. 當時間越來越遙遠時,理解代碼成了噩夢。本來只需要閱讀幾行的時候,卻因爲所有的代碼都雜在一起變成了要閱讀幾千行。時間一長,重新閱讀時,別說別人,就是自己也很難一下就能掌握關鍵點。
  3. 當需要做一個功能時,卻發現代碼無法複用,明明是一樣的邏輯也只能靠Ctrl+CCtrl+V。這又爲今後修改代碼時增加了工作量。
  4. 當需要測試時確發現很難進行測試,每次修改一處代碼後都必須進行重複的人工測試,無法進行自動化測試,模塊和模塊也無法拆開來進行獨立的單元測試,每次都要整體的測一遍才能確保一切完好如初。

要換的不是架構,而是思維方式

其實目前市面上的架構模式已經有很多種,各有不同,但模式終究只是一種設計理念的表現形式,學習再多的架構,你也只是多會用了幾種工具而已,學習一種模式其實是在學一種思維方式:

如何在解決問題的時候把問題合理的拆分,又如何將拆分的零件合理的組裝成解決問題的工具。

將這種思維方式深入到你的大腦裏,血液裏,直至骨髓,讓它成爲你思考問題的一種習慣和定式,逐漸成爲你的一部分,那麼這時你已達到無招勝有招的境界了。

閒話先不扯了,回正題。

實現架構沒有唯一標準

這裏首先需要說明的是無論MVP或MVC還是MVVM等任何一種架構和模式其實都沒有誰優誰劣之分,而且就算是同一種架構,也可以根據不同的使用場景來做不同的實現方式,這裏並沒有宇宙絕對的對錯標準和概念定義。這和張三丰在教無忌太極拳以後讓其先忘掉招式是一樣的道理,在應用型領域,定式和概念只應是在學習的過程中才存在的,而真正學會以後應該馬上忘掉定式,忘掉概念,將其用熟用活纔是關鍵。

所以說我在此描述的概念也不是唯一的標準,而只是個人對其的理解。

什麼是MVC

因爲MVP是MVC的一個變種,因此我們先來看在MVC裏代碼是如何拆分和組合的,這裏簡要的描述常見的定義:

  1. 拆分:Model負責數據,View負責顯示,Controller負責用戶輸入。
  2. 組合:View將用戶操作和事件傳遞給Controller,Controller將用戶命令翻譯成消息來傳遞給Model,Model在處理完數據後,通知View來顯示給用戶,而View又從Model取出數據。

我認爲在學習MVP之前,必須深刻理解什麼叫做MVC,因爲兩者的區別其實沒有你想象中的那麼大,與其神話並盲目追崇新的架構,期望其能解脫你於苦海,還不如深刻的理解MVC的設計理念,這就和學習一門新語言一樣,只有你真正深刻的理解了其思維方式,那麼學習新的一門語言其實都是易如反掌的。因爲它們其實都是在做一件事,只是做的過程上有些許差異而已。

更多MVC的理解,可以參考我之前寫的一篇文章:淺談MVC

什麼是MVP

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

而這樣做的目的主要是爲了將數據和展示劃出更明確的界限。

首先我們來看MVP到底是在解決什麼問題的:

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

可以看出其實一個GUI程序的核心,還是在於用戶和數據之間的關係。而架構的引入也是爲了解決這幾個核心的問題。

那麼MVP又是如何解決這幾個問題的,Model,View,Presenter三者又各自負責什麼呢?誰來負責請求網絡數據,訪問和存儲本地數據,誰來負責處理數據,誰來負責顯示數據,誰又來負責和用戶交互呢?

具體表現在代碼上,也可以說:網絡請求應該放在哪,數據庫訪問應該放在哪,業務邏輯應該放在哪裏,處理用戶輸入應該放在哪,誰又來控制顯示或隱藏View的等具體的細節問題。

帶着這些具體問題,我們一起來學習。

如何拆分

首先來看MVP各自負責什麼:

  1. Model,負責定義數據(解決什麼是數據)
  2. Presenter, 負責在Model和View之間,從model裏取出數據,格式化後在View上展示(解決如何把數據和用戶界面放在一起)。
  3. View,負責擔任一個被動界面,用於展示數據。(解決如何展示數據)

和MVC比較而已,這裏出現一個最大的疑問就是:那麼誰來負責和用戶的操作交互呢?答案是,用戶在View上的所有操作(事件)都由View路由給Presenter,由Presenter來與其交互。

而和MVC最大的區別在於Model和View完全被Presenter隔開了,Presenter作爲它們之間的中間人去傳遞所有數據。

如何組合

三者又是如何組合起來的呢?

很顯然Presenter作爲中間者,它是同時擁有View和Model的引用的,爲了在它們之間起到橋樑作用,即Presenter會主動和View和Model進行通信

Model和View必須是完全隔離的,不允許兩者之間互相通信,保持對彼此的不感知,這樣的好處是你徹底將數據和展示分離來開,並且可以獨立的爲Model去做測試。

Model在三者中是獨立性最高的,Model不應該擁有對View的引用,而且Model也不需要保存對Presenter的引用,對於Presenter而已,Model只需要提供接口,等着Presenter來調用時返回相應數據即可,這和經典MVC模式中是非常不同的,在MVC中Model在數據發送變化後,是需要發送廣播來告之View去更新用戶界面的,而在MVP中,Model是不應該去通知View,而是通知Presenter來間接的更新View的。

Presenter和Model的關係也應該是基於接口來通信,這樣才能把Model和Presenter的耦合度也降到最低,那麼在需要改變Model內部實現,甚至徹底替換Model的時候,Presenter則是無需隨之改變的。這樣做帶來的另一個好處就是你可以通過Mock一個Model來對Presenter以及View做模擬測試了,從而提高了可測試性。

那麼View和Presenter的關係呢?View是需要擁有對Presenter的引用,但僅僅是爲了將用戶的操作和事件立即傳遞給Presenter,爲了讓View和Presenter耦合較低,View也只應該通過接口與Presenter通信,從而保證View是完全被動的,一方面它由用戶的操作觸發來和Presenter通信,另一方面它完全受Presenter控制,唯一需要做的事情就是如何展示數據。

簡要總結三者之間的關係是:View和Model之間沒有聯繫,View通過接口向Presenter來傳遞用戶操作,Model不主動和Presenter聯繫,被動的等着Presenter來調用其接口,Presenter通過接口和View/Model來聯繫。

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

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

Talk is cheap, show me the code

爲了便於理解,這裏提供一些僞代碼加註釋演示一下如何實現MVP模式:

View

<code class="java" style="padding: 0px; border: currentColor; border-image: none; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 12px; background-color: transparent;"><span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">interface</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserView</span> </span>{

  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">setPresenter</span><span class="hljs-params">(presenter)</span></span>;
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showUsers</span><span class="hljs-params">(users)</span></span>;
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showDeleteUserComplete</span><span class="hljs-params">()</span></span>;
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showDeleteUserError</span><span class="hljs-params">()</span></span>;

}

<span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">class</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">UserView</span> <span class="hljs-keyword" style="color: rgb(133, 153, 0);">implements</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserView</span> </span>{

  UserPresenter presenter;

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 保持對Presenter的引用,用於路由用戶操作</span>
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">setPresenter</span><span class="hljs-params">(presenter)</span> </span>{
      <span class="hljs-keyword" style="color: rgb(133, 153, 0);">this</span>.presenter = presenter;
  }

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 將Presenter傳遞來的數據展示出來</span>
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showUsers</span><span class="hljs-params">(users)</span> </span>{
      draw(users);
  }

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// Model操作數據成功後,通過Presenter來告之View需要更新用戶界面</span>
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showDeleteUserComplete</span><span class="hljs-params">()</span> </span>{
      alert(<span class="hljs-string" style="color: rgb(42, 161, 152);">"Delete User Complete"</span>);
  }

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// Model操作數據失敗後,也是通過Presenter來告之View需要更新用戶界面</span>
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">showDeleteUserError</span><span class="hljs-params">()</span> </span>{
      alert(<span class="hljs-string" style="color: rgb(42, 161, 152);">"Delete User Fail"</span>);
  }

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 當用戶點擊某個按鈕時,將用戶操作路由給presenter,由presenter去處理</span>
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">onDeleteButtonClicked</span><span class="hljs-params">(event)</span> </span>{
      presenter.deleteUser(event);
  }

}</code>

Model

<code class="java" style="padding: 0px; border: currentColor; border-image: none; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 12px; background-color: transparent;"><span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">interface</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserModel</span> </span>{

  <span class="hljs-function">List<User> <span class="hljs-title" style="color: rgb(38, 139, 210);">getUsers</span><span class="hljs-params">()</span></span>;
  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">boolean</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">deleteUserById</span><span class="hljs-params">()</span></span>;

}

<span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">class</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">UserModel</span> <span class="hljs-keyword" style="color: rgb(133, 153, 0);">implements</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserModel</span> </span>{

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 在數據庫裏查找數據,並將數據返回給presenter</span>
  <span class="hljs-function">List<User> <span class="hljs-title" style="color: rgb(38, 139, 210);">getUsers</span><span class="hljs-params">()</span> </span>{
       <span class="hljs-keyword" style="color: rgb(133, 153, 0);">return</span> getUsersInDatabase(id);
  }

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 在數據庫裏刪除數據,並將結果返回給presenter</span>
  <span class="hljs-function">User <span class="hljs-title" style="color: rgb(38, 139, 210);">deleteUserById</span><span class="hljs-params">(id)</span> </span>{
      <span class="hljs-keyword" style="color: rgb(133, 153, 0);">return</span> deleteUserByIdInDatabase(id);
  }

}</code>

Presenter

<code class="java" style="padding: 0px; border: currentColor; border-image: none; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; font-size: 12px; background-color: transparent;"><span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">interface</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserUserPresenter</span> </span>{

  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">deleteUser</span><span class="hljs-params">(event)</span></span>;

}

<span class="hljs-class"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">class</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">UserUserPresenter</span> <span class="hljs-keyword" style="color: rgb(133, 153, 0);">implements</span> <span class="hljs-title" style="color: rgb(181, 137, 0);">IUserPresenter</span> </span>{

  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 保持對View的引用</span>
  IUserView view;
  <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 保持對Model的引用</span>
  IUserModel model;

  UserUserPresenter(IUserView view, IUserModel model) {
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">this</span>.view = view;    
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">this</span>.model = model;

    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">this</span>.view.setPresenter(<span class="hljs-keyword" style="color: rgb(133, 153, 0);">this</span>);   
  }

  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">start</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 從Model中取出數據</span>
    List<User> users = model.getUsers();
    <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 將數據發送給View,讓其展示到用戶界面</span>
    view.showUsers(users);
  }

  <span class="hljs-function"><span class="hljs-keyword" style="color: rgb(133, 153, 0);">void</span> <span class="hljs-title" style="color: rgb(38, 139, 210);">deleteUser</span><span class="hljs-params">(event)</span> </span>{
    <span class="hljs-comment" style="color: rgb(147, 161, 161);">// View將用戶操作路由過來,由Presenter來處理</span>
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">long</span> uid = whichUserNeedToDeleteBy(event);
    <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 將用戶操作翻譯成命令或消息傳遞給model,以改變數據</span>
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">boolean</span> success = model.deleteUserById(uid);
    <span class="hljs-comment" style="color: rgb(147, 161, 161);">// 將Model操作數據後的結果通知View來改變用戶界面</span>
    <span class="hljs-keyword" style="color: rgb(133, 153, 0);">if</span> (success) {
          view.onDeleteUserSuccess();
    } <span class="hljs-keyword" style="color: rgb(133, 153, 0);">else</span> {
        view.onDeleteUserFail();  
    }
  }
}</code>

OK,到此一個最簡單的MVP模式就實現了,可以看到整個架構的輪廓已經很清晰了,而且面向接口編程也帶來了很多的好處,讓代碼之間藕和較少,對測試支持也更爲友好,理解和維護起來也更加方便了。

Android裏面實現MVP模式,http://blog.csdn.net/lmj623565791/article/details/46596109

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章