1, 前提
可以說MVVM是專為WPF打造的模式, 也可以說MVVM僅僅是MVC的一個變種, 但無論如何, 就實踐而言, 如果你或你的團隊沒有使用"Binding"的習慣, 那麼研究MVVM就沒有多大意 義.
另外,個人覺得, 使用Command以及打造一種合理的簡化的方式去使用Command也與使用 Binding一樣重要.
2, 誕生
為了解決現實世界中的問題,我們需要將現實世界中的事物加以抽象, 然後得到了 Domain Object, 無論貧血的還是富血的, 我們都可以簡單地把他們歸結為"由現實世界抽 象出來的模型", 也就是我們的model, 也就M-V-VM中的"M"。
但其無法與我們的用戶進行交互, 所以, 我們需要為其創建一個界面(視圖, View), 該視圖可以與用戶輸入設備進行交互, 這很棒, 但問題是如何將View與我們的model關聯 起來? Binding便可以發揮作用了, 比如視圖上的某一個文本框中的文本和Model中的"用 戶名"關聯起來, 用戶便可以通過操作該文本框來訪問和修改Model的"用戶名"了。
這是極其簡單的情況, 但實際編程時我們發現, Model中的屬性(與方法)往往不那麼容 易與View中的界面控件關聯起來, 比如, "類型不匹配": 界面控件所需要的類型與模型中 屬性提高的類型不匹配. "需要額外操作": 模型中的數據需要經過一些額外的處理才能傳 給視圖,反之亦然. 此時, 我們意識到View似乎需要一個"Helper"類來處理一些額外工 作.
這個helper所包含的代碼可以放在除了Model外的很多地方(我們現在不考慮貧血富血 之類的爭論), 比如View中, 記得自己剛學習窗體程序開發時就是這麼干的, 將絕大多數 處理邏輯放在那個所謂的CodeBehind中. 後來,正如大家在各種設計模式書籍中所看到的 一樣,為了將View和Model剝離開來,實現view可替換(比如你可以講自己精心設計的軟件同 時運行於窗體程序,Web甚至Mobile上), 便有了MVC. 有了MVC以後似乎就開始滋生M-V-XXX 之類的爭論與變種模型, 比如MVP以及這裡的MVVM,甚至MVP也有著Supervising Controller與Presentation Model兩種方式. 但主要圍繞兩個問題,一是model與view之間 的關系, 完全隔離的?單向的還是雙向的? 二是這個"XXX"需要完成哪些功能,簡單流程調 度?復雜規則處理? OK,這些爭論都沒有關系, 是否采用某種模式取決於你的開發所處的環 境(比如語言特性,框架特性)以及你的業務特性以及所面臨的主要變化點等等。
但與MVC,MVP所不同的是,MVVM的引入不僅僅是技術上的原因(解除耦合應對變化等老生 常談),另外一個很大原因是:軟件團隊開發方式的改變.如果你做過一段時間的WPF項目開 發的話,你可能會有比較明顯的感覺:在View層打造上,如何分配程序員和美工的工作.在繼 續閱讀之前,大家可以看看我以前的一篇文章"在UI Designer與Developer之間". 以前我 們團隊采用的便是"集成模式", 我便兼職了其中的"Integrator"角色.這還不錯.但說實在 的,這僅僅是一個在特殊情況下不得已而為之的暫時方案,所以我們付出了很大的努力開始 轉向"收割模式"了,要轉向這個模式,至少需要兩個基本條件:
(1)你擁有能夠熟練運用Blend等工具能為程序員輸出XAML的美工, 他專注於純粹的 UI/UE, 另外他還必須具有一定的"程序員"思維.以便輸出的東西能很好地作為程序的一部 分而運轉起來,而不是僅僅"看上去"是那樣的。
(2)你需要能夠脫離View層但仍能編寫出高質量代碼的程序員。
幸運的是, 我們在努力創造條件1,並取得了很好的效果.(你可以招一個具有Flash腳本 編寫經驗的並且有極大的學習熱情的美工人員, 並對他進行Blend的相關培訓). 而MVVM模 式為我們實現第二個條件提供了極大的便利. 為什麼MVC/MVP模式不行而MVVM可以呢? 很 簡單, 在MVC和MVP模式中, View層都具有很多代碼邏輯, 開發View層的是程序員, 雖然 UI/UE團隊會做很多工作, 但這個層的"實現者"仍然是程序員. 在以前的開發中,其工作得 很好, 而在WPF開發中程序員對View層的展現顯得力不從心了,美工(指符合上面條件1的美 工)雖然很擅長, 但他會說"可惜我不會程序".於是, 我們需要一種方式將View層的代碼邏 輯抽取出來,並View層很純粹以便完全讓美工去打造它.相應地, 需要將View層的相應邏輯 抽取到一個代碼層上,以便讓程序員專注在這裡。
回想一下, 我們只所以要在View(Xaml)背後寫一些代碼(C#), 無非是想傳遞一些數據 以及傳遞數據時的數據的處理或在用戶與界面控件進行交互時執行一些操作, 最簡單的例 子是在MVC中當界面發生交互時View去調用Controler中的某個方法, 以便將該操作的相應 "指示"傳遞到"後台"去. 在以前的技術中, 這樣的"銜接性"的代碼是必須的. 而在WPF中, 則可以通過另外的技術來進行層與層之間的"銜接", 這就是"Binding" 和"Command", 以 及稍後我們會提到的"AttachBehavior". 通過Binding, 我們可以實現數據的傳遞; 通過 Command, 我們可以實現操作的調用.(AttachBehavior的作用稍後再談). Binding和 Command是可以寫在XAML中的, 這樣看來XAML後面對於的CS文件可以被完全拋棄或不予理 會了. 這樣的XAML文件正是美工所需要的. 而這些對於Binding以及Command的定義描述以 及其他相關信息的代碼應該放在那裡呢, 當然不是View, 更不是Model, 是"ViewModel". ViewModel是為這個View所量身定制的, 它包含了Binding是所需的相關信息,比如 Converter以及為View的Binding提供DataContext, 它包含了Command的定義以便View層可 以直接使用, 另外,它還是一個變種的Controler, 它得負責業務流程的調度。
於是, 便有了這副圖, 然後, 正如"時勢造英雄"所言, MVVM就誕生了.
3, ViewModel 與單元測試
如果你是一名正在使用MVVM模式打造軟件的程序員, 那麼我勸你盡快忘掉View. 你所 面對的是這樣一個模式"UnitTest-ViewModel-Model"(這並非一個模式, 僅僅是我為闡述 觀點而暫時如此表述的)。
記得曾經有一個Model-View-AbstractView模式, 而MVVM中的VM實際也是一個 AbstractView: the abstraction of view. 它是一個抽象的View, 具有一個View的靈魂, 而不具備相應的可視化控件而已. 所以對於程序員而已, 打造這樣一個抽象的VM就可以認 為是完成View層的打造了.而當美工完成無數控件組成的實際的View後, 我們就可以用 Binding和Command這樣的黏合劑將這個抽象的View和實際的View黏合在一起了。
那麼在黏合之前, 我們怎麼知道自己的VM是否正常工作呢? 單元測試!
在說明對於ViewModel進行單元測試的重要性之前, 送給大家一句話: "View and Unit Test are just two different types of ViewModel consumers" (Josh Smith). 如果我 們將ViewModel看作生產者, 那麼View和Unit Test都是具有同等地位的消費者而已. 並且 UnitTest相比於View而言具備更大的消費能力. 或者你可以簡單的認為View也僅僅是一種 不太推薦的測試方式而已. 所以要實施好這個模式, 那麼對ViewModel的單元測試就是必 須的了,並且這個測試要不依賴於任何UI控件. (那麼不是不對應ViewModel的開發是不是 就應該通過測試來驅動了?TDD?)
4, AttachBehavior
一般情況下利用Command, Binding, AttachProperty等WPF特性, View和ViewModel之 間能配合工作得很好. 假設我們有一個Button, 當該Button被點擊的時候我們要完成一 些操作, 很簡單, 將該操作封裝成一個Command並綁定到該Button上就可以了, 但如果我 們要在Button被Load的時候執行另外一些操作呢?由於Button沒有直接被Load事件所觸發 的Command, 所以不能使用Command了. 不能直接將Load事件處理器寫在Button所在的Xaml 所對應的CS文件裡, 這和我們剛才對MVVM的設計是相矛盾的. 一個不太好的方案是繼承一 下Button, 並撰寫一個由Load所觸發的Command, 這可行, 但明顯不好. 正如一個控件沒 有某個屬性並且在不繼承的情況下而采用AttachProperty一樣, 我們可以采用 AttachBehavior. AttachBehavior不是WPF特性, 它僅僅是一個最佳實踐, 一個Pattern. 關於AttachBehavior語法如何書寫, 請參考 : http://www.codeproject.com/KB/WPF/AttachedBehaviors.aspx