大話MVP
之前寫了一篇名稱為《談談關於MVP模式中V-P交互問題》的文章,主要表達本人對於MVP模式下(主要針對Passive View變體)View和Presenter之間的關係,以及它們之間的交互應該采用怎樣的原則和方式的看法。園子裏的朋友對此展開了一些討論,尤其是是一個叫做非空的朋友轉述了另一篇文章提出的關於CAB中關於MVP模式的14條規則,和本人的觀點有很多相似之處,當然也有一些不一致的地方。為此,在本篇文章中,就此進行一些必要的補充。
一、CAB(Componsable Application Block)基於14條MVP規則
為了讓所有的人都能夠閱讀上麵提及的那篇文章,我將其轉載我個人的博客中,有興趣的讀者可以仔細閱讀(《Design Rules for Model-View-Presenter》)。文章作者提出的觀點和我在《談談關於MVP模式中V-P交互問題》中的觀點是一致的,即Presenter對於View應該是相對透明的,View不能直接對Presenter進行操作,目的是實現Presenter和View之間的分離(The generated code is not quite as I would like it, I prefer that the view has no knowledge of the presenter and no direct access to it either as this gives a cleaner separation between views and presenters)。文章作者傾向采用事件注冊的方式實現Presenter和View,不過我不太清楚具體是注冊View還是Presenter的時間。在《談談關於MVP模式中V-P交互問題》中對MVP的實現手段,我采用的是在Presenter注冊View的事件。關於View和Presenter的分離,我的做法作得更加徹底一點——根本就不給開發者從View調用Presenter的機會。
文中提出了關於CAB的14條編寫符合MVP規範的規則,在這裏我特將其翻譯成中文:
1、所有的View(包括View的接口)的名稱應該以View作為後綴,比如TaskView/ITaskView;
2、所有的Presenter名稱應該以Presenter作為後綴,比如TaskViewPresenter;
3、Presenter完成Use Case處理邏輯,對GUI控件的處理應該在View中實現;
4、View調用Presenter的方法應該像觸發事件異常,通過調用OnXxx方法的方式來實現;
5、應該盡可能地限製View對Presenter的調用,並且調用的方式限於按照“事件”的形式,比如_presenter.OnViewReady();
6、View不允許通過Presenter直接調用Model和Service,並且Presenter的方法應該是不具有返回值的;
7、Presenter必須通過View接口的方式調用View
8、除了對View接口成員的實現外,View中的其他方法不應該是public的;
9、除了CAB ModuleController 對View的加載和限製外,View隻能被Presenter調用;
10、View接口方法應該基於Use Case的邏輯起一個有意義的名稱,比如SetDataSource這樣的方法名稱是不合法的;
11、View接口的成員應該僅限於方法,不應該包含屬性;
12、所有的數據應用保持在Model中
13、定義在View接口的方法不應該包含對GUI空間名稱的引用(比如AddExplorerBarGroup),因為這會使Presenter知道View太多關於實現方麵的細節;
14、盡量讓View的方法名稱反映Use Case的業務邏輯,這樣可以使你的代碼具有自表述性並更加易於理解。
再次回到《談談關於MVP模式中V-P交互問題》中討論的話題,在我看來,拋開1和2對View的Presenter命名的規範外,其餘的12條規則體現了MVP關於View和Presenter之間應該具有的關係,以及我們應該采取的正確的Presenter和View交互方式。View和Presenter之間的關係,可以通過對Presenter的角色界定來體現,在整個MVP體係中Presenter扮演的是協調者的角色。
二、Presenter是協調者,是整個MVP體係的控製中心
如果我們將MVP體係比喻成一個社團(考慮到中國沒有黑社會,這裏我們說社團),我們經常看見的往往是那些頂著黃毛,紋著紋身的街頭小混混,你可以將它們看成是View。也就是說View是和外界打交道的人,是行動者,就像是到處砍人、收保護費,以及和別的社團搶地盤的都是這些處於社團基層的小混混一樣。View永遠處於處於幕前,和最終用戶進行交互,但是地位卻不高。對於用戶的UI交互請求該如何進行處理,View做不了主,它需要向大佬匯報。所以View永遠不可能是決策者,僅僅是一個匯報者而已。
Presenter才是真正的大佬、話事人,執龍頭杖的。Presenter生藏不露,最終用戶感知不但它的存在,就像社團大哥大都隱藏的比較好,甚至以政府官員(比如文強大哥)或者是電影公司老板(比如香港的XXX電影公司)的身份出現。但是,我們知道,他才是整個社團的主導、核心,是整個事務的決策者和執行者,使能夠調動相關資源的協調者,而這個事務,你可以理解為Use Case。也就是說,Presenter是對Use Case的反映,UI交互邏輯的處理流程定義在Presenter中,但是具體的實現並不是完全在Presenter中,這一點很重要,下麵一節中我們還會談到。
我們還是把話題回到交互上麵。這裏的交互,即View和Presenter之間如果溝通,是比較特別的。談到溝通,很多人都會認為這是一個雙向的問題,而View和Presenter采用單向的溝通方式,這和某些上下級的溝通方式有點類似——下級單方地向上級匯報工作,上級單方的向下級下達命名。這在等級觀念深重社團中更是如此,我們習慣的場景是這樣的:小混混向大佬說:“我們的場子昨天晚上被砸了,懷疑是XXX幹的”。大佬說:“恩,知道了,下去吧!”。真正有前途的小混混不會說“我們的場子昨天晚上被砸了,懷疑是XXX幹的,我們什麼時候去砍他?”。真正有範兒的大佬不會馬上命令你在什麼時候、什麼地點、帶多少兄弟去砍人,而在計劃實施的時候會向相關成員下達砍人的指令。
反映在真正View|Presenter的交互上麵,就是說:View單純地將用戶的交互請求匯報給Presenter;Presenter接收到請求之後,整合相應的資源、執行相應的處理邏輯。對處理流程的某一個步驟,如果設置到業務邏輯和數據模型,則調用Model,如果涉及到對GUI控件的操作,還會調用View。View將交互請求遞交給Presenter之後,不需要考慮後續需要做什麼,因為Presenter會在適當的時候命令View該如何做。
所以說,Presenter是整個體係的驅動著,View和Presenter不應該是一種拉的關係,而是一種推的關係。View將用戶交互請求推給Presenter,Presenter將數據推給View並驅動View完成相應的UI相應。 正因為如此,上麵的MVP規則列表中才規定Presenter的方法不需要返回值,View的接口不需要定義屬性。實際在我個人看來,Presenter和View接口都應該隻包含返回類型為void的方法即可。
三、Presenter不關注具體的實現細節
談到這裏有人會說,所有的關於UI處理邏輯定義在Presenter中,那麼會不會使Presenter變得臃腫不堪呢?持這種觀點的人實際上走入了另外一個誤區。我曾經看到過有人寫過這樣一個極端的例子——將View的所有控件都以屬性的方式公布出來,定義在View接口中,所有控件相關的操作都實現在Presenter中。很明顯這是不對的,雖然這個例子很極端,但是我想很多對MVP不是太了解的人或多或少會犯這種錯誤。
在上麵一節中,我們說過UI交互邏輯的處理流程定義在Presenter中,但是具體的實現並不是完全在Presenter中。Presenter是藍圖的設計者,並不關注實現的細節。大佬隻是製定行動計劃,真正砍人、收保護費和搶地盤這種操作性強的工種的還是屬於小混混們。
所以該View幹的事一件也逃不了,隻是View不考慮什麼時候幹,因為Presenter會在適當的時候通知你,View得保證隨叫隨到。為了保證Presenter能夠有效地控製View,需要將這些操作定義在接口中。既然定義在接口中,操作的粒度就不能太細。Presenter關於的是整個Use Case的處理流程,所以定義在View接口中的操作也應該采用處理流程相關的語言來定義。
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 15:04:42