Windows Presentation Foundation (WPF) 和 Silverlight 提供了丰富的 API 用来构建现代应用程序,但是了解并和谐一致地应用所有 WPF 特性来构建设计精良、易于维护的应用程序可能非常困难。从何处入手?什么样的方法才算是正确的应用程序设计方法?
Model-View-ViewModel (MVVM) 设计模式描述了构建 WPF 或 Silverlight 应用程序的常用方法。它还是一款构建应用程序的强大工具,以及一种与开发人员讨论应用程序设计的通用语言。虽然 MVVM 确实很有用,但它发展时间不长,用户尚未形成正确的认识。
MVVM 设计模式什么时候是适用的,什么时候又是不必要的?应该如何设计应用程序的结构?ViewModel 层有多少代码要编写和维护,有什么替代方式能够减少 ViewModel 层的代码量?如何妥善处理 Model 中的相关属性?应该如何在 View 中显示 Model 中的集合?应该在哪里实例化 ViewModel 对象,并将其挂接到 Model 对象?
在本文中,我将解释 ViewModel 的工作原理,并讨论在您的代码中实现 ViewModel 的优缺点。我还会介绍一些具体的示例,演示如何使用 ViewModel 作为文档管理器,以便在 View 层中显示 Model 对象。
Model、ViewModel 和 View
到目前为止,我设计过的每个 WPF 和 Silverlight 应用程序都具有相同的高层组件设计。Model 是应用程序的核心,需要投入大量精力,按照面向对象的分析和设计 (OOAD) 最佳做法进行设计。
对我来说,Model 是应用程序的核心,代表着最大、最重要的业务资产,因为它记录了所有复杂的业务实体、它们之间的关系以及它们的功能。
Model 之上是 ViewModel。ViewModel 的两个主要目标分别是:使 Model 能够轻松被 WPF/XAML View 使用;将 Model 从 View 分离并对 Model 进行封装。这些目标当然非常好,但是由于一些现实的原因,有时并不能达到这些目标。
您构建的 ViewModel 知道用户在高层上将如何与应用程序交互。但是,ViewModel 对 View 一无所知,这是 MVVM 设计模式的重要部分。这使得交互设计师和图形设计师能够在 ViewModel 的基础上创建优美、有效的 UI,同时与开发人员密切配合,设计适当的 ViewModel 来支持其工作。此外,View 与 ViewModel 的分离还使得 ViewModel 更有利于单元测试和重用。
为了在 Model、View 和 ViewModel 层之间实施严格的分离,我喜欢将每一层构建为一个单独的 Visual Studio 项目。与可重用的实用工具、主要的可执行程序集以及任何单元测试项目(您有大量这些内容,对吗?)结合之后,这会产生大量项目和程序集,如图 1 所示。
图 1 MVVM 应用程序的组成部分
由于这种严格分离的方法会产生大量项目,因此它显然最适合大型项目。对于只有一两位开发人员的小型应用程序来说,这种严格分离带来的好处可能无法抵消创建、配置和维护多个项目所带来的不便,因此仅仅将您的代码分离到同一个项目的不同命名空间中,可能比充分隔离更好用。
编写和维护 ViewModel 并不容易,不应轻率地对待。但是,一些最基本问题(MVVM 设计模式什么时候是适用的,什么时候又是不必要的)的答案经常包含在您的域模型中。
在大型项目中,域模型可能非常复杂,需要精心设计数以百计的类,使它们能够在任何类型的应用程序(包括 Web 服务、WPF 或 ASP.NET 应用程序)中顺畅地结合在一起。Model 可能由几个相互配合的程序集组成,甚至在超大型组织中,域模型有时是由一个专门的开发团队构建和维护的。
如果您有一个复杂的大型域模型,则引入 ViewModel 层几乎总是会带来好处。
另一方面,域模型有时很简单,可能仅仅是覆盖在数据库上的一个薄层。类可以自动生成,而且通常会实现 INotifyPropertyChanged。UI 通常是一系列可供编辑的列表或表格,允许用户对底层数据进行操作。Microsoft 工具集一直都极其擅长轻松快捷地构建这类应用程序。
如果您的模型或应用程序是这种类型的,则 ViewModel 很可能会带来难以接受的高开销,而对您的应用程序设计并没有足够的好处。
尽管如此,即使在这些情况下,ViewModel 也仍然有其价值。例如,ViewModel 非常适合用来实现“撤消”功能。另外,您也可以选择在应用程序的某个部分(例如文档管理,我将在后面讨论)使用 MVVM 直接向 View 提供 Model。
为什么要使用 ViewModel?
即使 ViewModel 看起来适合您的应用程序,在开始编写代码之前,仍然还有问题需要解答。其中最重要的问题是如何减少代理属性的数量。
MVVM 设计模式将 View 从 Model 分离,这种做法是该模式的一个重要且有价值的方面。因此,如果 Model 类有 10 个属性需要在 View 中显示出来,则 ViewModel 最终通常会有 10 个等效的属性,这些属性只是代理了对底层模型实例的调用。这些代理属性在设置时通常会引发属性更改事件,通知 View 该属性已更改。
并非每个 Model 属性都要有 ViewModel 代理属性,但是每个需要在 View 中显示的 Model 属性通常都有一个代理属性。代理属性通常如下所示:
public string Description {
get {
return this.UnderlyingModelInstance.Description;
}
set {
this.UnderlyingModelInstance.Description = value;
this.RaisePropertyChangedEvent("Description");
}
}