文章: MVVM启示录

标签: 文章 mvvm 启示录 | 发表时间:2012-04-03 13:00 | 作者:
出处:http://pipes.yahoo.com/pipes/pipe.info?_id=10560380f804c7341f042a2b8a03e117

细究MVVM

熟悉WPF或Silverlight的同学应该不会对MVVM模式感到陌生了,它把应用程序划分成视图、视图模型和模型三层,如图1所示:

图 1

表面上,这个层次结构还蛮清楚的,但如果你细究每层应该包含什么,事情就没那么简单了。

视图应该是最容易理解的一个部分了,它通常是指用户可以看到的界面,一般都是通过XAML代码来实现的。但是,XAML代码并不能实现一切想要的效果,这个时候很多人会把目光投向代码隐藏文件,在里面通过事件处理程序实现某些效果。那么,究竟什么样的代码可以放在代码隐藏文件里?这些代码是否包含原本应该放在视图模型里的代码?这些效果是否还有其他途径可以实现?

视图模型从字眼上看应该是视图的抽象,这意味着每个视图都应该有一个对应的视图模型,这也是我们经常看到的做法。不过,有人对此表示反对,他们认为视图和视图模型不是一一对应的关系,整个应用程序应该有一个主要的视图模型,各个视图将会绑到这个主要的视图模型上,同时有一些次要的视图模型,用来表示诸如配置等辅助方面。那种做法才是正确的?视图模型里面又该包含什么样的代码?

最后是模型,大多数人对它的一个共识就是,它会包含具体的数据,这些数据最终会显示到用户界面上。问题是,它到底是简单的POCO还是业务逻辑的复杂类型?我们是否应该在这里放置验证逻辑?是否允许它和视图直接绑定,还是需要另外创建对应的包装类?如果使用LINQ to SQL存储数据的话,模型里的类是否就是LINQ to SQL的实体类?如果需要访问Web Service获取数据,模型里的类和添加Web Service时自动生成的类又是什么关系?

哇,问题还真不少啊!你是否曾经遇到这些问题?你对它们有什么想法?诚然,这些问题没有唯一的标准答案,它们都是开发者在具体实践中提炼和总结出来的智慧,但它们通常只针对于特定的应用场景,或者说,它们是为了满足某些需要而产生的。举个例子吧,有些人可能会觉得添加Web Service时自动生成的类和他们要创建的模型类基本上是一致的,为了避免重复劳动,他们选择直接使用那些自动生成的类,这种做法一般不会出现问题,直到由于需求的变更,模型不再和Web Service对应起来,但此时应用程序的其他部分已经通过这些自动生成的类和Web Service紧密耦合起来了,修改应用程序就可能变得非常困难。相反,如果应用程序的功能比较单一、专注,Web Service的接口也比较稳定,那么特意为那些自动生成的类创建一组一模一样的模型类显然增加劳动成本,埋下潜在的维护问题。

最简单的实现

假设我经常去图书馆借书,我需要一个应用查看所有图书的归还日期,如图2所示:

图 2

这是一个非常简单的应用,从MVVM模式的角度来看,图2所展示的用户界面就是视图了,而视图模型和模型也都非常简单,分别为图3的MainViewModel类和Book类:

图 3

页面的ListBox控件将会绑到MainViewModel的Books属性,书名将会绑到Book的Title属性,而归还日期则绑到Book的DueDate。

到目前为止,一切都非常自然顺畅,直到我对它提出两个新的需求:

  • 图书列表根据归还日期从小到大排序,即最先要还的书拍在最上面。
  • 今天和明天要还的书字体使用强调色。

这两个新的需求都非常合理。第一个需求属于页面的抽象逻辑,不与页面的任何控件挂钩,这种需求一般会在视图模型里面实现,具体地就是在MainViewModel的构造函数里初始化Books属性时进行升序排序。

至于第二个需求,它涉及到具体的TextBlock控件以及对Book类的DueDate属性的二次处理,原则上不应该在Book类里面实现,根据个人偏好,这个需求有两种不同的实现方式:

  • 创建一个ItemViewModel类,包装Book类并暴露相关属性,同时提供一个Foreground属性用于和TextBlock控件的对应属性绑定,Foreground属性可以在初始化ItemViewModel时根据Book的DueDate属性计算。
  • 通过转换器实现相同的效果。

有人说,使用MVVM模式可以消除转换器的需要,是的,任何时候当你需要一个转换器,你都可以通过创建包装类并提供额外的属性获得相同的效果,但我们不应该把这个问题绝对化,转换器的存在价值体现在可以在不同的绑定关系上重用相同的逻辑,而且更符合Expression Blend用户的使用习惯。

看到这里,有些同学可能会问,如果用户要求同时提供根据图书标题和归还日期两种排序方式呢?每当我们遇到一个新的需求时,请不要马上动手实现或者考虑如何实现,应该先想想用户为什么有这样的需求。根据归还日期进行排序这个需求对应着帮助用户避免逾期归还所受的惩罚,但根据图书标题排序呢?很多时候,我们会想当然地认为用户需要某些功能,而忽略用户真正的需求,这样不但会导致功能冗余,还会分散用户对于最重要功能的注意。事实上,根据图书标题排序这个需求很可能是想帮助用户了解某本书是否已经存在于列表中,或者某本书的具体信息,如还可以读多久,本质上,这个需求很可能是帮助用户从列表中快速查找某本书。如果是这样,为什么不考虑给出一个即时搜索的功能,比如说,当用户单击搜索按钮时,会显示一个搜索框,用户在里面输入关键字,图书列表马上显示包含该关键字的图书?

命令与操作

到目前为止,这个应用几乎可以说一无是处,因为它不支持添加、编辑和删除等操作,那么,实现这些操作又有哪些东西需要考虑呢?假设我们在用户界面上添加相应的按钮和菜单,如图4所示:

图 4

以往的做法是在代码隐藏文件里通过事件处理程序来实现,但在MVVM模式里,我们提倡通过命令对象来实现,问题是,这些命令对象在哪实现?添加操作是页面范围的,与之对应的命令对象可以在MainViewModel类里实现,但编辑和删除两个操作对应于Book类,那么,我们是否应该在Book类里添加相应的行为?

有一部分人对此的观点是,模型并非单纯的POCO,而是完整的领域模型,可以包含区别于页面逻辑的业务逻辑,并且不会和ORM的实体类等同起来,这样做的好处是我们有一个统一的地方来维护整个应用的状态,也和具体的数据层解耦,无论数据最终来自本地还是远程服务,都不会影响在此之上的东西,与此同时,我们也不必在为不同的视图模型之间如何传递数据感到烦恼。当然,这样做的坏处也是明显的,它引入了大量可能不必要的复杂性,对于小型项目具有不少杀伤力。

如果我们不在Book类里添加行为,又不想在代码隐藏文件里通过事件处理程序来实现这些操作,那么我们就需要考虑一下Expression Blend的行为(Behavior)了。具体的想法是这样的,假设用户单击编辑菜单项的时候将会打开EditItemPage.xaml页面,而这个页面需要知道用户选中哪本图书,那么整个操作就可以看作通过NavitagionService.Navigate方法打开“EditItemPage.xaml?title=XXX”这样的链接了。要实现这样的效果,你可以使用 AppBarUtils for Windows Phone SDK 7.1的NavigateWithQueryStringAction,如代码1所示:

<AppBarUtils:NavigateWithQueryStringAction TargetPage="/EditItemPage.xaml"> 
<AppBarUtils:Parameter Field="title" Value="{Binding Title}"/> 
</AppBarUtils:NavigateWithQueryStringAction> 

代码 1

配合EventTrigger在MenuItem上使用就可以实现预期的效果了。

除此之外,你也可以考虑创建一个ItemViewModel类,然后在上面实现编辑操作的命令对象,然后和MenuItem的Command属性进行绑定。如果你选择这种做法,就会无可避免地遇到在视图模型里打开页面的问题。我们通常用来打开页面的NavitagionService.Navigate方法必须在页面的范围内才可访问,但视图模型对于视图一无所知,怎么调用这个方法?

常见的做法是封装PhoneApplicationFrame的Navigate方法。当你用Visual Studio创建一个Windows Phone项目时,App类里面会有一个RootFrame属性,你可以通过这个属性调用PhoneApplicationFrame的Navigate方法。事实上,PhoneApplicationFrame和页面是共用同一个NavitagionService对象的。

删除操作是一种很特别的操作,它同时涉及到集合以及里面的元素,但在XAML里,MenuItem只能从父元素继承对应的Book对象,却无从知晓包含该对象的集合,这为实现删除操作造成极大困扰。常见的解决办法是把MainViewModel作为一个静态属性放在App类里,这样你就可以轻易访问到包含Book对象的Books集合。从这个角度来看,如果我们一开始就把模型设计成领域模型,负责管理和维护领域对象的状态,那么现在就不必把某个视图模型硬塞到App类里了。

应用程序栏以及其他

在Windows Phone上使用MVVM模式必定会遇到的一个障碍就是应用程序栏,它的问题在于它不是Silverlight控件,而是系统组件,这意味着它无法像通常的Silverlight控件那样进行数据绑定。市面上有不少解决方案,其中之一就是前面提到的 AppBarUtils for Windows Phone SDK 7.1,有兴趣的可以看看Allen Lee写的 《AppBarUtils使用指南》

如果你把模型设计成领域模型,那么你一定要注意Windows Phone的“深度链接”(deep link),这种情况会在你使用Toast通知和次要磁贴(secondary tile),并在用户单击打开应用的某个页面时出现。由于用户仅对某个页面感兴趣,而且当用户按返回键时会直接退出应用而不是按照应用的常规逻辑返回上一页,因此构建整个领域模型会显得劳师动众、耗费资源。

有人认为,使用MVVM模式的一大好处是为Expression Blend用户带来便利,确实是这样,数据绑定和命令对象的应用使得Expression Blend用户更易通过可视化操作使用开发人员的后台代码。如果你的视图模型也会给Expression Blend用户使用,那么你必须考虑的一点就是在视图模型里提供设计时数据,尤其是你的逻辑包含访问本地数据库或者Web Service,因为在Expression Blend的设计器里无法执行这些代码。

最后不得不提的是,MVVM模式使得我们可以绕过用户界面对应用的功能进行测试,包括单元测试,如果你有兴趣,可以看看Chenkai的 《Windows phone 应用开发[9]-单元测试》


感谢 侯伯薇对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com。也欢迎大家通过新浪微博( @InfoQ)或者腾讯微博( @InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

相关 [文章 mvvm 启示录] 推荐:

文章: MVVM启示录

- - InfoQ cn
熟悉WPF或Silverlight的同学应该不会对MVVM模式感到陌生了,它把应用程序划分成视图、视图模型和模型三层,如图1所示:. 百度技术沙龙第二十五期:海量数据处理技术解析(4月7日 周六). 表面上,这个层次结构还蛮清楚的,但如果你细究每层应该包含什么,事情就没那么简单了. 视图应该是最容易理解的一个部分了,它通常是指用户可以看到的界面,一般都是通过XAML代码来实现的.

[Architecture] MVP, MVC, MVVM, 傻傻分不清楚~

- Amo - 點部落-小朱® 的技術隨手寫

Instagram启示录

- Wenhuan - 所有文章 - UCD大社区
不管认为Instagram是lomo-twitter还是poladroid+iphoneography. 照片分享服务Instagram于 6个月之前上线,上个月24日公开发布了支持实时功能的API,第三方开发人员可以根据标签、地点和地区抓取照片. 数据显示,Instagram目前每周新增13万用户.

原生启示录

- stingzou - 互联网的那点事...
之前写过一篇关于andoid和ios对比的文章——乔布斯改变移动未来的人. 人们通常认为苹果是靠iphone卓越的外观设计轻松取得5%的移动市场份额和他人望其项背的利润,但老乔自视苹果是一个缔造优质软件的企业. 他曾夸口苹果在软件上的水平要领先业界5年. 今年ipad2发布的时候 乔布斯还是没免俗的演示自己开发的应用.

天使用户启示录

- 书皮 - 互联网的那点事...
天使用户就是一个产品最早那批使用者中最认同产品,并希望更多人认同这个产品的人. 对于创业者来说,他们就像天使投资一样,对产品和企业有着至关重要的意义. 8月23日,Gmail工程经理马克·斯奇贝克(Mark Striebeck)在其博客中发布了代号为“麒麟”的集成功能的截图. “麒麟”计划是谷歌应广大用户要求将Google+ 整合至Gmail中的新举措,从而尝试让Google+与公司现有网络服务无缝链接,借助于此,谷歌进军社交网络迈出了又一实质性步伐.

昙花一现:Color启示录

- pestwave - 36氪
想想你花4100万美元举办一个聚会,结果没有一个人出现你会怎么样. 今年3月份的时候,该公司刚刚推出手机照片分享服务甚至连一个用户都还没有就获得了4100万美元的融资. 尽管高调开局,但是反响非常落寞. 也正是自那以后,投资者开始反思是不是泡沫真的出现了,因为许多创业公司连做出的产品有没有用户用都不知道就能获得如此天价的投资.

产品经理的启示录

- Gary - 乱象,印迹
曾经有个流传甚广的问题:前些年程序员都想去做项目经理,现在都想去做产品经理了,这是为什么呢. 我看到的一个答案是:因为程序员都被产品经理折磨疯了. 这是一个许多人都赞同的答案,而且从此细想开去,可以发现很多问题:早先的程序员,并不是不会被产品经理折磨,而是几乎根本没有产品经理来折磨. 在开发还主要服务于具体问题,以定期发布一版软件为主要形态的阶段,功能的有与无是最大的问题;而在开发深入到生活的细节领域,计算机用来解决各种问题,持续发布成为常态,竞争又日趋激烈的情势下,产品的重要性才日渐凸显出来——我们都习惯了不仔细翻阅说明书,凭直觉使用各种功能,我们也习惯了在系统的各种“提示”下直抵问题的核心.

Instagram-Color 启示录【4】社区营造法式

- 冉波 - 爱范儿 · Beats of Bits
宋人所著 “营造法式”  是中国历史上第一部建筑工程官方规范. Color Labs 首轮融资的金额高达 4100 万美元,这个数字引起不少争论. 很多用户反映,Instagram 比 Color 更好用,图像处理效果也更炫. 但是为什么 Instagram 只获得 700 万. 4100 万比 700 万高出近 5 倍,Color 有什么样的杀手锏,值这么多钱.