在WPF系(包括SL,WP或者Win8)應用開發中,MVVM是個老生常談的問題。初學者可能不會有感覺,但當你寫一個核心邏輯能在各種平台上無縫移植,而只需改改UI的時候,那種快感是無法用語言來形容的。
筆者當初接觸時,對MVVM並不以為然,編了很多代碼以后,反過來看MVVM for WPF的經典文章以后,才若有頓悟。標准的MVVM把程序分成了Model, ViewModel和 View三個部分,但方法是死的,人是活的。我一般的做法是邏輯寫一個,View寫一個,沒有那么嚴格。為了方便討論,我們把ViewModel和Model合稱Model, View還是View, 分別代表邏輯和界面。分離是肯定的,可是在程序中終究是要把View和Model在某個地方結合起來。 本文就討論下幾種結合的方式。
1. 標准MVVM(由View實例化Model)
標准的MVVM,做法當然是先設計Model, 然后再設計View, 在View的代碼里有且僅有這么幾句話:
public partial class PluginMangerUI : UserControl { public PluginMangerUI() { this.InitializeComponent(); PluginManager manager = new PluginManager(); this.DataContext = manager; } }
基本的邏輯結構可以用下圖來表示。不同的庫是由自底向上的方式設計的。
這種由View調用Model, 並具體由View負責Model實例化的方式是最為普遍的,非常適合於需要跨平台的應用。當然,Model並不知道View的存在,因此View要承擔所有的界面邏輯,好在WPF已經給出了足夠多的解決方案,觸發器,模板。基本絕大多數需求都能滿足。
2. 插件結構(Model實例化View)
這種做法,是筆者經常用的。在Model里,通過以下代碼來實現界面生成
internal class ViewExample : UserControl { } public class ModelExample : IView { private readonly ViewExample view; public ModelExample() { this.view = new ViewExample(); } public object UserControl { get { return this.view; } }
肯定會有同學問道,怎么會有這么奇怪的寫法?這種做法的最常見場合應該是插件系統。一個個的Model其實是一個個的插件,它們應該具備自治性。因此,應該由自身負責界面的產生。
它的好處是可以通過Model更加精細的調節View的行為,你可以在任何時候獲得View內部ListBox的SelectIndex, 而不用麻煩的用Binding。 打個比方說,游戲開發中,你需要隨時控制物體的運動速度和方向,這樣Model就必須控制View. 綁定很難解決這類問題。
我不知道有多少同學在WPF中使用插件的設計思想。若按插件的思路,庫應該按功能划分。在這種設計思路下,不同的庫便不是自下而上的分層了,而是通過領域和功能分層,如下圖:
每一個功能庫都有完整的自治性,當你將該功能庫拷貝到主框架之下時,它就會自動加載,由Model負責View的生成。一切合情合理。
3. 組裝車間(第三方組裝View和Model)
這種思路來自於工廠方法,類似於裝配車間,View和Model都不負責互相的實例化。而有一個“管理器”負責組裝它們。這樣的好處在於可配置。你可以通過配置文件動態的改變View.
我記得一種比較著名的WPF向導(Wizard)就是這樣的設計思路:
private static List<CompleteStep<DataProcessTask>> CreateSteps(DataProcessTask o) { var welcomeModel = new WelcomeModel(o); var step1ViewModel = new UserCoreModel(o); var step2ViewModel = new UserDataModel(o); var step3ViewModel = new ConnectModel(o); var step6ViewModel = new FinishModel(o); return new List<CompleteStep<DataProcessTask>> { /// Each step contains a ViewModel and a View type (the type representing the actual Xaml to be shown). new CompleteStep<DataProcessTask> {ViewModel = welcomeModel, ViewType = typeof (Welcome), Visited = true}, new CompleteStep<DataProcessTask> {ViewModel = step1ViewModel, ViewType = typeof (UserCore)}, new CompleteStep<DataProcessTask> {ViewModel = step2ViewModel, ViewType = typeof (UserDataView)}, new CompleteStep<DataProcessTask> {ViewModel = step3ViewModel, ViewType = typeof (Connect)}, new CompleteStep<DataProcessTask> {ViewModel = step6ViewModel, ViewType = typeof (Finish)} }; }
如上圖所示,DataProcessTask類是控制整個流程的核心,第一步先實例化所需的ViewModel, 第二部,通過構建一個List列表,將View的Type的方法傳到列表中。最終管理器通過反射來實例化View,並將DataContext綁定到對應的ViewModel完成整個組裝過程。
可以看出,這種做法徹底的隔絕了View和Model, 同時通過配置選項,可以隨時修改View。可謂是一種不錯的設計。 但是,必需看到,對於View來說,Model沒有任何管理的權限。下圖展示了它的基本邏輯:
如果最終你依舊需要兩邊互相控制,可以考慮采用dynamic關鍵字。
4. 總結
其實沒有哪種方式是最好的,完全是看你對整個系統的設計需求。但不論如何,界面和邏輯的分離,這是毋庸置疑的。下面的表格總結了幾種做法的特點和適用場合:
名稱 | 組裝邏輯 | 適用場合 | 缺點 | 備注 |
標准MVVM | View實例化Model | 常用的跨平台場合 | Model無法控制任何View | 適用於自底向上的分層設計 |
Model實例化View | Model實例化View | 插件結構或用於游戲開發 | 存在一定的耦合 | 適用於按功能划分的插件型類庫設計,或要求Model大量控制View的場合 |
組裝車間 | 第三方管理器實例化和組裝Model和View | 可動態替換所有View | 兩者徹底隔絕,沒有控制靈活性 | 大型系統的嚴格設計 |
當然,如果用MVVMLight等第三方類庫的話,就應該按照它的方案去開發。但我們的原則是,解決問題,但不要引入更復雜的問題。為了解耦,搞了大量的復雜邏輯,反而舍本逐末。
有任何問題,歡迎隨時討論。