一、背景:與用戶界面 (UI) 相關的最大的問題就是大量的凌亂的代碼,原因兩個:
(1) 用戶界面包含負責的邏輯用於維護界面相關對象;
(2) 其次也包含了應用程序狀態的維護。
用戶界面的3大問題:狀態 (State) , 邏輯 (Logic) ,同步 (Synchronization),其中狀態是用戶界面最關心的問題之一。
二、簡述MVC、MVP、MVVM
(1) MVC:模型-視圖-控制器(Model View Controller),它強制性的使應用程序的輸入、處理和輸出分開。
(2) MVP:模型-視圖-表現類(Model-View-Presenter)
(3) MVVM:模型-視圖-視圖模型(Model-View-ViewModel)
三、比較
(1) 發展過程:MVC->MVP->MVVM
(2) MVC->MVP
MVC中Model不是純Model,因為它要有View的一些數據結構。
(3) MVC、MVP->MVVM
View沒有大量代碼邏輯。結合WPF、Silverlight綁定機制,MVP演變出了MVVM,充分利用了WPF、Silverlight的優勢,將大量代碼邏輯、狀態轉到ViewModel,可以說MVVM是專門為WPF、Silverlight打造的。
(4)用戶界面問題比較
MVC |
MVP |
MVVM |
||||
V |
C |
V |
P |
V |
VM |
|
狀態 |
√ |
√ |
√ |
|||
邏輯 |
√ |
√ |
√ |
|||
同步 |
√ |
√ |
√ |
四、MVVM
(1) 組成部分Model、View、ViewModel
(a) View:UI界面
(b) ViewModel:它是View的抽象,負責View與Model之間信息轉換,將View的Command傳送到Model;
(c) Model:數據訪問層
(2) View與ViewModule連接:
(a) Binding Data:實現數據的傳遞
(b) Command:實現操作的調用
(c) AttachBehavior:實現控件加載過程中的操作
Binding和Command可以寫在XAML中。
(3) 優勢
(a) ViewModule易於單元測試;
(b) View沒有MVC、MVP復雜的代碼邏輯,讓整個開發過程中的UI設計和后台的代碼編寫完全分開,設計者可以專注於使用Express Blend等去設計頁面也就是View,而開發的可以完全通過Model來定義要操作的object,通過ViewModel來定義出來需要對這些model做哪些操作,最后使用Command來把Model和View完全聯系到一起。
(4) 不足
(a) 編寫Command的任務重;
(b) 由於Silverlight不能引用非Silverlight項目,許多界面層的邏輯也得放到后台(非ViewModel部分),如Command實現,必須通過WCF通信調用服務。
(5) ICommand
(a) 編寫每一個需要綁定的Command
(b) 網上有寫好的基於WPF的Command模板,利用Prism特性重用這部分。
(c) 直接利用Prism的DelegateCommand和CompositeCommand類
DelegateCommand接受Delegate參數
CompositeCommand可以將多個Command組合在一起。
注:Prism 是微軟最佳實踐,可以簡化建設WPF和Silverlight應用。
(6) Command原理
(7)AttachBehavior
(a) 背景
假設我們有一個Button, 當該Button被點擊的時候我們要完成一些操作, 很簡單, 將該操作封裝成一個Command並綁定到該Button上就可以了, 但如果我們要在Button被Load的時候執行另外一些操作呢? 由於Button沒有直接被Load事件所觸發的Command, 所以不能使用Command了. 不能直接將Load事件處理器寫在Button所在的xaml所對應的CS文件里, 這和我們剛才對MVVM的設計是相矛盾的. 一個不太好的方案是繼承一下Button, 並撰寫一個由Load所觸發的Command, 這可行, 但明顯不好. 正如一個控件沒有某個屬性並且在不繼承的情況下而采用AttachProperty一樣, 我們可以采用AttachBehavior.
五、MVVM具體應用
(1) Command工作流程:
主要使用Prism的DelegeCommand、CompositeCommand類與訂閱、發布原理。
(a) ViewModel初始化命令(ICommand)
public DelegateCommand<object> AddAdministorCommand { get; private set; }
this.AddAdministorCommand = new DelegateCommand<object>(this.AddAdministor);
(b) View UI元素綁定ViewModel Command屬性
Command="{Binding Path=AddAdministorCommand}"
(c) 在ViewModel中定義一個事件(event),實現CompositePresentationEvent<object>接口(object-傳遞參數)。
public class AdministorRequestEvent: CompositePresentationEvent<AdministorViewModel>
{ }
(d) 使用TheAggregator訂閱一個事件的執行者(Action)
this.TheAggregator.GetEvent<AdministorRequestEvent>().Subscribe(ShowAdministorVie w);
public void ShowAdministorView(AdministorViewModel administorVM)
{
AdministorView administorV = new AdministorView();
administorV.ViewDataContext = administorVM;
administorV.Show();
}
(e) 使用發布一個事件
private void AddAdministor(object obj)
{
this.TheAggregator.GetEvent<AdministorRequestEvent>().Publish( new AdministorViewModel(Administor.CreateNewAdministor(),_administorRepository));
}
表現層持續解耦帶來的模式轉變 MVC MVP MVVM (微軟WPF帶來的團隊變化和軟件技術變化)
Model-View-ViewModel是一種架構模式,主要在WPF、Silverlight和WP7開發里使用,它的目標是從視圖層移除幾乎所有代碼隱藏(code-behind)。交互設計師可以專注於使用XAML表達用戶體驗需求,然后創建和視圖模型的綁定,而視圖模型則是由應用程序開發者開發和維護的。
MVVM是更加通用的Presentation模式的一個具體實現。MVVM視圖模型包含概念模型而不是數據模型,所有業務邏輯和其它操作都是在模型和視圖模型里完成的。
MVVM 把應用程序的狀態和行為進行封裝,隔離了用戶界面和用戶體驗部分,從而使得設計者與開發者可獨立工作,並易於協作。開發者可迅速的投入代碼開發中,只關注應用邏輯即可。如果實現一些相對較小,后期變化不大的應用程序,可能並不關注設計模式,使用設計模式反而帶來復雜度。但ViewModel卻具備良好的伸縮性,在小型應用中也可帶來不菲的好處。它還易於在最終的應用程序中利用開始構建的原型系統,使得異步編程變得簡單等等。
在MVP模式中,為了讓UI層能夠從邏輯層上分離下來,設計師們在UI層與邏輯層之間加了一層interface。無論是UI開發人員還是數據開發人員,都要尊重這個契約、按照它進行設計和開發。這樣,理想狀態下無論是Web UI還是Window UI就都可以使用同一套數據邏輯了。
與用戶界面相關的最大的問題就是大量的凌亂的代碼,
a) 用戶界面包含負責的邏輯用於維護界面相關對象;
b) 包含了應用程序狀態的維護;
表現模式 (Presentation patterns) 就是圍繞如何移除用戶界面的復雜性,讓界面更加簡潔和可管理而產生的,下圖就是常見表現模式的種類與分類:
(1) MVC:模型-視圖-控制器(Model View Controller),它強制性的使應用程序的輸入、處理和輸出分開。
(2) MVP:模型-視圖-表現類(Model-View-Presenter)
(3) MVVM:模型-視圖-視圖模型(Model-View-ViewModel)
(a) 發展過程:MVC->MVP->MVVM
(b) MVC->MVP
(c) MVC、MVP->MVVM
用戶界面的3大問題:狀態 (State) , 邏輯 (Logic) ,同步 (Synchronization)
- 狀態 (State) : 狀態是用戶界面最關心的問題之一。狀態是用戶界面數據的當前快照,在 Web 應用中,可能是 Session 級別的一個變量,在 Windows 應用中, 則可能只是界面級別的數據。 用戶界面包含的狀態越多, 則用戶界面越復雜。
- 邏輯 (Logic) : 用戶界面往往包含界面邏輯,例如維護文本框、組合框或者其它任何界面元素,用戶界面中這種邏輯越多,則用戶界面越復雜。
- 同步 (Synchronization) : 用戶界面通常需要和業務組件協作,因此用戶界面需要在界面元素與業務對象之間同步數據,如果用戶界面包含的同步任務越多,則用戶界面越復雜。 .
表現設計模式 (Presentation Design Pattern)
表現設計模式有助於解決上面列出的問題, 它的的基本邏輯就是創建一個額外的表現類 (Presenter) ,用來消化用戶界面中復雜的邏輯,數據和同步的問題,從而使得用戶界面變得簡單明了。根據這個類承擔責任的多少,決定了表現設計模式的類型,可能是 SC , PV , PM 等,也就是說,這個類的成熟度決定了它將是那種設計模式。
表現模型 (PM)
- 表現類包含邏輯
- 表現類包含狀態
- 表現類代表抽象的用戶界面
- 表現類不關注用戶界面
- 視圖關注表現類
- 視圖與業務模型完全隔離
MVVM
- 繼承自表現模型
- 使用 WPF 以及 Silverlight 的綁定機制
MVC
- 沒有表現類,有控制器 (Controller)
- 請求首先到達控制器
- 控制器負責綁定視圖與業務模型
- 邏輯存在於控制器中
MVVM的提出源於WPF,主要是用於分離應用界面層和業務邏輯層,以前的ASP.Net三層架構是Web架構,MVVM是Windows應用程序架構。WPF用Xaml繪制界面,繪制完的界面是個獨立的文件,文件里包含界面所有的樣式和行為(行為是對行動的一種封裝)。
為什么使用 MVVMMVVM實際上是三層架構,M層(Model實體層)、V層(View表示層,它有DataContext屬性,這個屬性可以使用DataTemplate模板綁定VM層的數據用來顯示)、VM層(ViewModel層,對Model層進行CRUD進行操作,同時對V層提供數據綁定)。
命令對象
漫談消息triggers,actions,behaviors
傳統的WinForm和ASP.NET應用程序是基於事件驅動開發的,以ASP.NET為例,在實際開發中,*.aspx頁面用於渲染HTML,*.aspx.cs頁面用於實現服務端邏輯,在開發初期,這種方式顯得方便快捷,但是這種高耦合性導致了后期維護的復雜性,一旦aspx變化,aspx.cs的代碼同時需要改變,比如將aspx中的GridView控件以FormView控件進行替換,對應的aspx.cs文件中不得不進行大量修改。而MVVM模式在WPF/SL應用程序中得以廣泛應用的原因是,WPF/Siverlight應用程序是基於數據驅動的開發的,網上曾有研究者在WinForm下實現MVVM模式與WPF進行對比,得出結論:WinForm項目中大規模運用MVVM模式開發效率很低。
與MVC,MVP所不同的是,MVVM的引入不僅僅是技術上解除耦合應對變化的原因,另外一個很大原因是:軟件團隊開發方式的改變.
為什么MVC/MVP模式不行而MVVM可以呢? 很簡單, 在MVC和MVP模式中, View層都具有很多代碼邏輯, 開發View層的是程序員, 雖然UI/UE團隊會做很多工作, 但這個層的"實現者"仍然是程序員. 在以前的開發中,其工作得很好, 而在WPF開發中程序員對View層的展現顯得力不從心了,美工雖然很擅長, 但他會說"可惜我不會程序".於是, 我們需要一種方式將View層的代碼邏輯抽取出來,並View層很純粹以便完全讓美工去打造它.相應地, 需要將View層的相應邏輯抽取到一個代碼層上,以便讓程序員專注在這里.
我們只所以要在View(Xaml)背后寫一些代碼(C#), 無非是想傳遞一些數據以及傳遞數據時的數據的處理或在用戶與界面控件進行交互時執行一些操作, 最簡單的例子是在MVC中當界面發生交互時View去調用Controler中的某個方法, 以便將該操作的相應"指示"傳遞到"后台"去. 在以前的技術中, 這樣的"銜接性"的代碼是必須的.而在WPF中, 則可以通過另外的技術來進行層與層之間的"銜接", 這就是"Binding" 、"Command"、"AttachBehavior".通過Binding,我們可以實現數據的傳遞; 通過Command,我們可以實現操作的調用.Binding和Command是可以寫在XAML中的, 這樣看來XAML后面對於的CS文件可以被完全拋棄或不予理會了.這樣的XAML文件正是美工所需要的. 而這些對於Binding以及Command的定義描述以及其他相關信息的代碼應該放在那里呢,當然不是View, 更不是Model, 是"ViewModel". ViewModel是為這個View所量身定制的, 它包含了Binding是所需的相關信息,比如Converter以及為View的Binding提供DataContext, 它包含了Command的定義以便View層可以直接使用,另外,它還是一個變種的Controler, 它得負責業務流程的調度.
在WPF/Silverlight中應用MVVM模式,View主要用於界面呈現,ViewModel用於邏輯實現,Model用於數據的構造,而這三者能夠進行通信,最重要的是通過WPF/Silverlight中強大的數據綁定機制,將View和ViewModel有效的聯系起來。
盡管在MVVM模式的名稱沒有體現Command,但是在實際情形中,Command是實現MVVM至關重要的一環,目前項目主要采用了Prism框架中的DelegateCommand<T>類
在Silverlight項目中采用MVVM模式,優勢是顯而易見的:
1,對於視圖-邏輯的分離便於后期對原有功能擴展和維護,當UI變化時,ViewModel中的邏輯不需要進行變化
2,可以僅僅通過Blend實現簡單的功能,而不需要寫任何代碼。
在實現過程中,不要只是為了實現MVVM而MVVM,而應該根據實際情況進行取舍,事實上,由於Silverlight只是WPF的一個子集,其對MVVM模式在某些方面的支持仍有所欠缺:
1,對枚舉類型綁定比較困難,如將枚舉類型綁定至RadioButton
2,Silverlight4中僅僅對繼承ButtonBase的控件實現了Command屬性,在實際的使用中,對於其它的事件可以使用Blend4中中的InvokeCommandAction
3,無法在View和ViewModel傳遞復雜對象,可以破壞View或者ViewModel作為折衷辦法,如ChildWindow和父容器的對象傳遞
有很多MVVM框架可以做到這點,其中一些是:
開源的
- PRISM:由微軟提供,和MEF/Unity一起用於依賴注入,支持組合命令,可以擴展。MSDN上有詳細的教程和演練。
- MVVM Light Toolkit:有visual Studio和Expression Blend的項目和項的模板。更多信息請看這里,另外可以參考VS和Expression Blend的使用教程。
- Caliburn Micro:支持視圖模型先行(ViewModel-First)和視圖先行(View-First)兩種開發方式,通過co-routine支持異步編程。
- Simple MVVM Toolkit:提供VS項目和項的模板,依賴注入,支持深拷貝以及模型和視圖模型之間的屬性關聯。
- Catel:包含項目和項的模板,用戶控件和企業類庫。支持動態視圖模型注入,視圖模型的延遲加載和驗證。還支持WP7專用的視圖模型服務。
閉源的
- Intersoft ClientUI:付費的,只支持WPF和Silverlight,但是,除了MVVM框架,它還提供其它一些特性。
- Vidyano:免費但不開源。帶有實體映射/虛擬持久化對象(數據容器),業務規則以及內置基於ACL的安全特性。
若想了解MVVM,可以參考以下資料:
- Laurent Bugnion的《Understanding MVVM Pattern》和《Deep Dive MVVM》
- 微軟Silverlight組的《Understanding the MVVM Pattern in Silverlight Applications》
- Erik Lebel在InfoQ上的視頻演講《Presentation Pattern》
使用MVVM的最大好處之一是分離關注點,以便用戶體驗設計師和應用程序開發者可以並行工作。另一方面,相關的擔憂包括它對於UI操作比較簡單的情況有點殺雞用牛刀的感覺,數據綁定有點難以調試,以及大量使用數據綁定可能帶來性能問題等等。
Jonathan Allen在評論里提到幾點錯誤使用MVVM的征兆:
1. 你的模型和視圖模型名字相同。
視圖模型不應該是對模型的包裝。視圖模型的職責是外部服務的請求中介,比如加載和保存數據。而數據本身,以及驗證和大多數業務邏輯應該放在模型里。
我經常強調這點。每當你創建一個視圖模型包裝一個模型,你就在你的API里引入一個巨大漏洞。具體地,任何直接引用這個模型的東西都可能以視圖模型無法察覺的方式改變某個屬性,因此UI也不會有相應的改變。同樣地,模型里計算字段的任何更改也不會回傳給視圖模型。
2. 你的視圖和視圖模型名字相同。
理想的情況下,視圖模型是不知道使用它們的視圖的,尤其是WPF應用程序有多個窗口共享相同的視圖模型。
對於比較小型的應用程序來說,整個應用程序可能只需一個視圖模型。對於比較大型的應用程序來說,主要功能可能需要一個視圖模型,每個次要方面也需要一個,比如配置管理。
3. 你沒有代碼隱藏。
代碼隱藏既非一個好的東西,亦非一個壞的東西。它只是一個用來放置和視圖或控件相關的邏輯的地方。因此,當我看到一個視圖沒有任何代碼隱藏,我就會馬上檢查是否存在以下問題:
- 視圖模型是否通過名字接觸了特定的控件?
- 視圖模型是否通過命令參數訪問控件?
- 是否使用了EventToCommand或其它可以導致泄露的行為而不是簡單的事件處理程序?
MVVM Light的EventToCommand很有問題,因為它會使得控件從屏幕移除之后無法被垃圾回收。
4. 視圖模型監聽屬性更改通知
如果一個模型的的生命周期比監聽它的事件的視圖模型長,那么可能導致內存泄露。不同於視圖有個Unloaded事件,視圖模型對於生命周期管理沒有很好的方案。因此如果它們關聯到存活期比它們更長的視圖模型的事件,視圖模型將會出現泄露。