應用MVP模式對遺留代碼進行重構

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