一 AV(Autonomous View)自治視圖
- 在面向終端用戶的應(yīng)用中,都需要一個可視化的UI來與用戶交互.這個UI稱為View 視圖.
- 在早期,我們習慣將所有前臺的邏輯,與視圖揉在一起,稱為AV 自治視圖.
- 這些邏輯包括:數(shù)據(jù)呈現(xiàn)(Display),用戶動作的撲捉與響應(yīng),數(shù)據(jù)存儲等.
- .Net 的Winform 和ASP.NET Web Form 采用的都是事件驅(qū)動模型.
- AV是將所有UI相關(guān)的邏輯都注冊到視圖本身,或者視圖元素對應(yīng)的事件上.
- 人機交互應(yīng)用的3個關(guān)注點.
- 數(shù)據(jù)在UI上的展示.
- UI處理邏輯.
- 業(yè)務(wù)邏輯.
- AV的缺陷
- 首先,業(yè)務(wù)邏輯與UI無關(guān),所以應(yīng)該最大程度地被重用.而在AV中,業(yè)務(wù)邏輯糅合在UI中,無法重用.例如,從winform遷移到web form上.
- 穩(wěn)定性:
業(yè)務(wù)邏輯>UI處理邏輯>UI.-
"短板效應(yīng)":?三者糅合在一起后,具有最弱穩(wěn)定性的UI決定了整體的穩(wěn)定性.
-
- 任何涉及到UI的組件都是不可測試性的(至少是很難測試).所以AV對測試不友好.
?二. MVC模式
針對AV的缺陷,采用SOC(關(guān)注點分離)來剝離3個部分.
-
將人機交互應(yīng)用分為3個部分:
- Model:對應(yīng)用狀態(tài)和業(yè)務(wù)功能的封裝.
- 維護著整個應(yīng)用的狀態(tài)(數(shù)據(jù)和行為),并實現(xiàn)了所有的業(yè)務(wù)邏輯,可以看做為一個領(lǐng)域模型.
- View:實現(xiàn)可視化界面的呈現(xiàn),捕捉最終用戶的交互操作(鍵盤,鼠標).
- Controller.
- View捕獲到用戶交互操作后會直接轉(zhuǎn)發(fā)給Controller,后者完成相應(yīng)的UI邏輯。
- 如果需要涉及業(yè)務(wù)功能的調(diào)用,Controller會直接調(diào)用Model。
- 在完成UI處理之后,Controller會根據(jù)需要控制原View或者創(chuàng)建新的View對用戶交互操作予以響應(yīng).
- Model:對應(yīng)用狀態(tài)和業(yè)務(wù)功能的封裝.
-
View和Model存在直接的聯(lián)系.View可以直接調(diào)用Model查詢其狀態(tài)信息。
- 當Model狀態(tài)發(fā)生改變的時候,它也可以直接通知View.
-
Model對View的數(shù)據(jù)狀態(tài)改變通知,View對Controller的用戶交互通知.都是單向的消息交換.
- 可以使用事件機制來實現(xiàn)這兩種通知.
- 也可以通過觀察者模式通過注冊/訂閱的方式來實現(xiàn).
- View 作為Model 的觀察者,通過注冊相應(yīng)的事件來檢測數(shù)據(jù)狀態(tài)的改變.
- Controller 作為View 的觀察者,通過注冊相應(yīng)的事件來處理用戶的交互操作.
三. MVP模式
-
MVC模式存在的問題
- View和Model可以繞過Controller來直接進行交互.
-
對于用戶驅(qū)動的程序(人機交互),我們不需要Model來主動通知View數(shù)據(jù)狀態(tài)的變化.所以,Model應(yīng)該是完全獨立的.
-
MVP模式的目標
- 測試(Unit Test)友好.
- 關(guān)注點分離.
- 正交性.
- 每一個操作都只改變一件事情,而沒有其它的副作用.
-
解依賴
- 對View 和Model 解耦.
- 降低了Presenter 對View 的依賴.從依賴于具體的View 到依賴于抽象的IView 接口.
-
交互
- Presenter對Model的單向調(diào)用.
- Presenter和View之間的雙向交互.這個是核心.
-
Presenter 和View 之間交互的方式
- PV(Passive View)
為了不做對UI的測試(難到幾乎不能),應(yīng)該在UI中不進行UI邏輯的處理.
一個被動的View.View中的UI元素(控件)不是由View本身操作,而是由Presenter控制對UI元素的操作.
需要將View中的元素以屬性或者其他方式暴露,以供Presenter操作.
-
在數(shù)據(jù)綁定中,控件類型的選擇應(yīng)該是View內(nèi)部的邏輯,不應(yīng)該出現(xiàn)在Presenter中.
- 所以,在IView的定義中,不能涉及到具體的控件類型.
- 而是返回一種數(shù)據(jù)綁定所需的數(shù)據(jù)類型.
- 然后在View內(nèi)部處理數(shù)據(jù)到控件的綁定.
PV對測試友好,因為所有的UI處理邏輯都在Presenter中,便于測試.
-
缺陷
- 對于一個復(fù)雜的UI(含有很多元素),IView接口將會十分龐大.
- Presenter需要對UI元素進行操作,所以要了解很多的UI細節(jié).造成簡單事情復(fù)雜化.
-
Soc
- 將諸如格式化,數(shù)據(jù)綁定這些簡單的UI邏輯移到View中.在View中進行一些簡單的UI邏輯處理.
- View本身僅實現(xiàn)單純獨立的UI邏輯,它處理的數(shù)據(jù)應(yīng)該是Presenter推送給它的.
- 所以View盡可能不維護數(shù)據(jù)狀態(tài).在Iview接口的定義中不包含屬性.
- Presenter所需的View狀態(tài)應(yīng)該是View在請求交互處理時給它的.
- PV(Passive View)
四. 第一次改造:最薄的View.
-
起源:由于View持有對Presenter的引用,所以理論上,View是可以無限制地調(diào)用Presenter的.
- 基于以前AV的編碼習慣,很可能造成以下的問題:
- 大部分(甚至所有)的UI處理邏輯都寫到View中.
- 而Presenter的作用就是Proxy,僅僅是調(diào)用View中的方法而已.
- 基于以前AV的編碼習慣,很可能造成以下的問題:
-
采用事件訂閱的方式來完成Presenter和View的交互.
- 首先,在IView中定義事件Handler.
- 為了隔離事件參數(shù)中e的類型污染(一些控件的事件參數(shù),會引入一些測試不友好的類型),定義一系列的事件參數(shù)類型.
- 然后,在View的控件事件處理函數(shù)中.
- 將處理事件需要的上下文信息,包裝到一個自定義的事件參數(shù)中,然后 Raise Event.
- 最后,在Presenter中,訂閱IView暴露的各種事件,并進行處理.處理時需要的上下文在自定義的事件參數(shù)中.
-
優(yōu)缺點
- View只完成了純粹的布局展示.
- 在事件處理流程中,如果需要Cancel處理,會比較難做到.
五. 第二次的改造
在View中調(diào)用Presenter的方法.完成部分的UI邏輯.
-
工程劃分(使用Company來替代真實信息).
- Company.MVP.ICommonView.
- 包含了對使用到的控件的抽象View接口,在每個接口中暴露出來Presenter需要使用到的屬性和函數(shù).
- 每一種控件類型一個接口.
- Company.MVP.ComonViews.
- 對于每一個控件,實現(xiàn)一個繼承了IXXXView接口的類.
- 在這些類中,體現(xiàn)了具體控件的屬性和方法的細節(jié).
- Company.MVP.Common.
- 該工程含有3個子文件夾.
- ModelObjects. Model的一部分,業(yè)務(wù)模型的抽象類.
- Service. Model的另外一部分,定義了數(shù)據(jù)訪問接口.
- View:定義了UI頁面需要實現(xiàn)的接口.
- 該工程含有3個子文件夾.
- Company.MVP.Presenter.
- Presenter的具體實現(xiàn).
- Company.MVP.Service.
- 數(shù)據(jù)訪問接口的具體實現(xiàn).
- Company.Client.
- 具體的UI工程.會實現(xiàn)Common中View的UI頁面接口.
- Company.MVP.ICommonView.
-
工程間依賴.
- Prensenter僅僅依賴于ICommonView和Common.而跟具體的UI控件類型,具體的UI畫面無關(guān).
- 所以,可以使用一個Presenter來對應(yīng)多個的View展示(Client).
-
單元測試
- 針對Presenter.
- 對于Service和View,由于P中操作的是兩者的接口.所以可以使用Mock來模擬這兩個部分.
- 而Model是可以簡單地New出來的,不需要進行Mock.
- 針對Model.
- 使用業(yè)務(wù)場景,進行測試.而且對其測試時,不需要進行Mock.
- 針對View.
- 可以進行少量的測試.因為有IView接口,所以可以Mock控件的屬性和行為,來針對UI頁面進行測試.
- 針對Presenter.
-
更換控件類型
- UI應(yīng)用中,最經(jīng)常遇到的情形.例如,現(xiàn)在要將界面上的一個TextBox控件替換為EditText控件
- 在UI實現(xiàn)的Client工程的具體頁面類上,將實例化以前的成員時使用的類型從TextBoxView修改為EditTextView即可.
- 其他的類和工程不需要修改.
- 改動被限定在了特定的地方.避免了短板效應(yīng).
- UI應(yīng)用中,最經(jīng)常遇到的情形.例如,現(xiàn)在要將界面上的一個TextBox控件替換為EditText控件
六. 總結(jié)
- 關(guān)于代碼量
使用MVP模式后,代碼量是肯定不會比原先的少的.
考慮到View的重用,以及子Presenter的重用.代碼量增加的也不多.
-
關(guān)于控件的View類型的接口抽象及實現(xiàn).
- 對于控件的View的接口,可以只針對一個頁面,也可以在工程前期,定義好對一個控件所需的所有的操作.這樣就在全系統(tǒng)中使用一份View的接口.
- View接口對外暴露的應(yīng)該是操作,而不是以控件屬性/方法的視角看待.也就是說Prensenter需要對控件進行什么類型的操作,就暴露一個這樣的操作出來.
-
關(guān)于控件差異性的問題.
- 系統(tǒng)中不同界面中,同一控件的操作接口可能是不同的.
- 按照MVP的本意,是沒有View重用的概念的.
- 但是,我們可以將同一控件基本的公用行為抽象為一個接口,然后使用一個類來實現(xiàn)它.然后在有特殊操作接口的畫面中,再定義一個繼承自公用接口的接口,然后使用一個類繼承公用類,并實現(xiàn)該接口.
-
關(guān)于控件的事件鏈.
- 在現(xiàn)有的代碼中,有很多地方用到了事件鏈的連鎖效應(yīng).
- 個人認為,這是一種不太好的編程方式.這樣控件之間相互的依賴關(guān)系變得如此的復(fù)雜.改動事件鏈上的任何一個控件的任何一個事件處理,都需要查看其連帶的連鎖反映.
- 在MVP中,我們在處理一個控件的操作時,會把所有控件需要展示的內(nèi)容一次性地處理好,然后一把交給View進行展示.而不是使用事件的連鎖效應(yīng).
- 這樣,就解除了控件之間在事件上的相互依賴關(guān)系.
-
關(guān)于單元測試.
- 對于業(yè)務(wù)系統(tǒng)的單元測試,純粹的代碼覆蓋率是沒有意義的.
- 需要關(guān)注的是測試的場景覆蓋率.
- 即使覆蓋百分百的代碼.但是漏測了一種Case,一樣會出現(xiàn)Bug.
- 所以,我們需要有很清晰的業(yè)務(wù)邏輯說明,來指導我們進行單元測試時的Case場景輸入.
-
事件處理流程三部曲
- IView中定義Event.
Event ButtonClick. - View中觸發(fā)事件.
- IView中定義Event.
Private withevents _item as button
Public sub itemClick() handles _item.Click
RaiseEvent ButtonClick
Presenter中掛接并處理事件
AddHandler OKButton.ButtonClick , Addressof Save.-
目標
- 一個(種)控件,對外提供統(tǒng)一的行為接口.
- 行為包括:屬性,方法,事件.
- 一個(種)控件,對外提供統(tǒng)一的行為接口.
-
畫面類職責清晰.
- 僅包含了控件的集合.
- 沒有任何的邏輯處理代碼.
-
更換控件類型時,改動最小.
- 僅需更改畫面類中New控時使用的實際View類型.
-
業(yè)務(wù)代碼和控件邏輯的分離.
- 業(yè)務(wù)代碼放在Model中.
- 控件邏輯,封裝在View的實際實現(xiàn)類中.
- Model是完全獨立的,不依賴于任何模塊.


