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