模型-视图-视图模型 (MVVM) 解释






4.94/5 (255投票s)
Model-View-ViewModel (MVVM) 模式简介。
引言
本文旨在介绍 Model-View-ViewModel (MVVM) 模式。虽然我在线上参与了许多关于 MVVM 的讨论,但我意识到正在学习该模式的初学者几乎没有什么可参考的资料,并且需要筛选大量相互矛盾的资源才能在自己的代码中实现它。我不想引入教条,而是希望将关键概念整合到一篇文章中,使其易于理解该模式的价值以及如何实现它。MVVM 实际上比人们想象的要简单得多。
背景
作为一名开发人员,你为什么要关心 Model-View-ViewModel 模式呢?这种模式为 WPF 和 Silverlight 开发带来了许多好处。在你继续阅读之前,请问自己:
- 你是否需要与设计师共享项目,并灵活地让设计工作和开发工作近乎同时进行?
- 你的解决方案是否需要彻底的单元测试?
- 在你的组织中,在项目内部和跨项目重用组件是否重要?
- 你是否希望在不重构代码库中其他逻辑的情况下,更灵活地更改用户界面?
如果你对其中任何一个问题回答“是”,那么使用 MVVM 模型可以为你的项目带来这些好处,而这仅仅是其中一部分。
我在线上读过的一些对话让我感到惊讶。比如,“MVVM 只对极其复杂的 UI 有意义”,或者“MVVM 总是增加大量开销,对小型应用程序来说太多了”。最令人震惊的是,“MVVM 无法扩展”。在我看来,这样的说法指的是对 MVVM 的知识和实现,而不是 MVVM 本身。换句话说,如果你认为搭建 MVVM 需要数小时,那说明你的做法不对。如果你的应用程序无法扩展,不要责怪 MVVM,而是责怪你使用 MVVM 的方式。将 10 万个项目绑定到列表框是愚蠢的,无论你遵循什么模式。
所以快速免责声明:这是我所了解的 MVVM,而不是一个普遍真理的 MVVM。我鼓励你使用评论分享你的想法、经验、反馈和意见。如果你觉得有什么不正确的地方,请告诉我,我会尽力保持这篇文章的更新和最新。
模型
模型是我喜欢称之为领域对象的。模型表示我们正在处理的实际数据和/或信息。模型的例子可能是一个联系人(包含姓名、电话号码、地址等)或实时流发布点的特征。
关于模型,关键要记住的是它包含信息,但不包含操作信息的行为或服务。它不负责格式化文本以使其在屏幕上美观,也不负责从远程服务器获取项目列表(实际上,在该列表中,每个项目很可能都是自己的模型)。业务逻辑通常与模型分开,并封装在操作模型的其他类中。这并非总是如此:例如,某些模型可能包含验证。
保持模型完全“干净”通常是一个挑战。我的意思是,它是“真实世界”的真实写照。例如,一个联系人记录可能包含上次修改日期和修改用户的身份(审计信息),以及一个唯一标识符(数据库或持久化信息)。修改日期对现实世界中的联系人没有实际意义,但它是模型在系统中如何使用、跟踪和持久化的功能。
这是一个用于保存联系人信息的示例模型
namespace MVVMExample
{
public class ContactModel : INotifyPropertyChanged
{
private string _firstName;
public string FirstName
{
get { return _firstName; }
set
{
_firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set
{
_lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
public string FullName
{
get { return string.Format("{0} {1}", FirstName, LastName); }
}
private string _phoneNumber;
public string PhoneNumber
{
get { return _phoneNumber; }
set
{
_phoneNumber = value;
RaisePropertyChanged("PhoneNumber");
}
}
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
public override bool Equals(object obj)
{
return obj is ContactModel && ((ContactModel) obj).FullName.Equals(FullName);
}
public override int GetHashCode()
{
return FullName.GetHashCode();
}
}
}
View
视图是我们大多数人都熟悉的,也是最终用户真正与之交互的唯一事物。它是数据的呈现。视图会进行一些自由处理,使数据更具可呈现性。例如,日期可能在模型上存储为自 1970 年 1 月 1 日午夜以来的秒数(Unix 时间)。然而,对于最终用户来说,它会以其本地时区的月份名称、日期和年份呈现。视图还可以具有与其关联的行为,例如接受用户输入。视图管理输入(按键、鼠标移动、触摸手势等),这些输入最终会操纵模型的属性。
在 MVVM 中,视图是活动的。与没有模型知识并完全由控制器/演示器操纵的被动视图不同,MVVM 中的视图包含行为、事件和数据绑定,这些最终需要底层模型和视图模型的知识。虽然这些事件和行为可能映射到属性、方法调用和命令,但视图仍然负责处理自己的事件,而不是完全将其交给视图模型。
关于视图,有一点要记住:它不负责维护自己的状态。相反,它会与视图模型同步此状态。
这是一个示例视图,表示为 XAML
<UserControl x:Class="MVVMExample.DetailView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot" Background="White"
DataContext="{Binding CurrentContact}">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Name:" HorizontalAlignment="Right" Margin="5"/>
<TextBlock Text="{Binding FullName}"
HorizontalAlignment="Left" Margin="5" Grid.Column="1"/>
<TextBlock Text="Phone:" HorizontalAlignment="Right"
Margin="5" Grid.Row="1"/>
<TextBlock Text="{Binding PhoneNumber}"
HorizontalAlignment="Left" Margin="5"
Grid.Row="1" Grid.Column="1"/>
</Grid>
</UserControl>
请注意,各种绑定是与视图模型的集成/同步点。
ViewModel
视图模型是三合体中的关键部分,因为它引入了呈现分离,或者将视图的细微之处与模型分离的概念。与其让模型知道用户对日期的视图,从而将其转换为显示格式,模型只是保存数据,视图只是保存格式化的日期,而控制器充当两者之间的联络员。控制器可能会从视图中获取输入并将其放置在模型上,或者它可能与服务交互以检索模型,然后转换属性并将其放置在视图上。
视图模型还公开方法、命令和其他点,这些有助于维护视图的状态,根据视图上的操作操纵模型,并在视图本身中触发事件。
MVVM 虽然“幕后”发展了相当长一段时间,但于 2005 年通过微软的 John Gossman 关于 Avalon(Windows Presentation Foundation,或 WPF 的代号)的博客文章向公众介绍。这篇博客文章题为《WPF 应用程序构建的 Model/View/ViewModel 模式简介》,从评论来看,人们理解它时引起了不小的轰动。
我听说 MVVM 被描述为专门为 WPF(以及后来的 Silverlight)设计的 演示模型 的实现。
该模式的示例通常侧重于用于视图定义的 XAML,以及用于命令和属性的数据绑定。这些更多是该模式的实现细节,而不是模式本身的内在部分,这就是为什么我用不同的颜色来突出数据绑定
这是一个示例视图模型的样子。我们创建了一个 `BaseINPC` 类(表示“`INotifyPropertyChanged`”),该类有一个方法,可以方便地引发属性更改事件。
namespace MVVMExample
{
public class ContactViewModel : BaseINPC
{
public ContactViewModel()
{
Contacts = new ObservableCollection<ContactModel>();
Service = new Service();
Service.GetContacts(_PopulateContacts);
Delete = new DeleteCommand(
Service,
()=>CanDelete,
contact =>
{
CurrentContact = null;
Service.GetContacts(_PopulateContacts);
});
}
private void _PopulateContacts(IEnumerable>ContactModel> contacts)
{
Contacts.Clear();
foreach(var contact in contacts)
{
Contacts.Add(contact);
}
}
public IService Service { get; set; }
public bool CanDelete
{
get { return _currentContact != null; }
}
public ObservableCollection<ContactModel> Contacts { get; set; }
public DeleteCommand Delete { get; set; }
private ContactModel _currentContact;
public ContactModel CurrentContact
{
get { return _currentContact; }
set
{
_currentContact = value;
RaisePropertyChanged("CurrentContact");
RaisePropertyChanged("CanDelete");
Delete.RaiseCanExecuteChanged();
}
}
}
}
这个视图模型显然旨在管理联系人列表。它还公开了一个删除命令和一个指示是否允许删除的标志(从而维护视图的状态)。通常,该标志将是命令对象的一部分,但示例是在 Silverlight 3 中,它不具备对命令绑定的原生支持,我希望展示一个不需要花哨框架的简单解决方案。(请观看此视频,了解将示例从 Silverlight 3 转换为 Silverlight 4 并使用原生命令是多么容易)。这里的视图模型对服务进行了具体引用。
对于大型应用程序,我更喜欢在外部连接该引用或使用依赖注入框架。好处是我们可以灵活地先这样构建,然后根据需要进行重构——同样,你无需使用任何这些框架即可利用该模式,正如你从这个例子中看到的那样。它立即获取“联系人”列表,这是一个硬编码的我和一个稍微更受欢迎的人的列表。当然,电话号码是伪造的。
让我们更具体一些,看看这在一个示例应用程序中是如何实现的。以下是一个示例 MVVM 设置的 X 射线图,可能看起来像这样
那么我们能从这张快照中获得什么呢?
首先,`IConfig` 表示一个配置服务(在新闻阅读器中,它可能包含正在获取的帐户信息和订阅源),而 `IService` 是“某个服务”——也许是新闻阅读器应用程序中从 RSS 源获取订阅源的接口。
视图和视图模型
- 视图和视图模型通过数据绑定、方法调用、属性、事件和消息进行通信
- 视图模型不仅公开模型,还公开其他属性(例如状态信息,如“忙碌”指示器)和命令
- 视图处理自己的 UI 事件,然后通过命令将其映射到视图模型
- 视图模型上的模型和属性通过双向数据绑定从视图更新
两种机制经常用于模式的实现:WPF 中的触发器(特别是数据触发器)和 Silverlight 中的视觉状态管理器 (VSM)。这些机制通过将 UI 行为绑定到底层模型来帮助实现该模式。在 Silverlight 中,VSM 应该是协调转换和动画的首选。了解有关 VSM 的更多信息。
视图模型和模型
在这种情况下,视图模型完全负责模型。幸运的是,它并不孤单
- 视图模型可以直接公开模型,或与模型相关的属性,用于数据绑定
- 视图模型可以包含服务、配置数据等的接口,以便获取和操纵它向视图公开的属性
先有鸡还是先有蛋?
你可能听说过关于“视图优先”或“视图模型优先”的讨论。一般来说,我相信大多数开发人员都同意一个视图应该只有一个视图模型。没有必要将多个视图模型附加到一个视图。如果你考虑关注点分离,这是有道理的,因为如果你在屏幕上有一个绑定到“联系人视图模型”的“联系人小部件”,以及一个绑定到“公司视图模型”的“公司小部件”,那么这些应该是单独的视图,而不是带有两个视图模型的单个视图。
一个视图可以由其他视图组成,每个视图都有自己的视图模型。视图模型在必要时可以组合其他视图模型(然而,我经常看到人们组合和聚合视图模型,而实际上他们真正想要的是视图模型之间的消息传递)。
虽然一个视图只能有一个视图模型,但一个视图模型可以被多个视图使用(例如,想象一个向导,它有三个视图,但都绑定到驱动过程的同一个视图模型)。
视图优先
视图优先简单地意味着视图驱动视图模型的创建或发现。在视图优先场景中,视图通常将视图模型绑定为资源,使用定位器模式,或者通过 MEF、Unity 或其他方式注入视图模型。这是管理视图和视图模型的一种非常常见的方法。以下是我关于该主题的一些文章
我在这篇文章中包含的例子是“视图优先”的。视图被创建,然后视图模型被附加。在 `App` 对象中,它看起来像这样
private void Application_Startup(object sender, StartupEventArgs e)
{
var shell = new MainPage();
shell.LayoutRoot.DataContext = new ContactViewModel();
RootVisual = shell;
}
在这个例子中,我保持简单,没有使用任何框架来连接接口和实现。
视图模型优先
视图模型优先是另一种将框架连接在一起的方法。在这种情况下,视图模型负责创建视图并将其自身绑定到视图。你可以在 Rob Eisenberg 在 MIX 上讨论的基于约定的框架中看到一个例子:构建你自己的 MVVM 框架。
这里的要点是,解决问题的方法不止一种。
一个基本的 MVVM 框架
在我看来,一个基本的 MVVM 框架实际上只需要两样东西
- 一个类,它要么是一个 `DependencyObject`,要么实现 `INotifyPropertyChanged`,以完全支持数据绑定,以及
- 某种命令支持。
第二个问题存在于 Silverlight 3 中,因为提供了 `ICommand` 接口,但未实现。在 Silverlight 4 中,命令功能更加“开箱即用”。命令有助于将视图中的事件绑定到视图模型。这些是使使用 MVVM 模式更容易的实现细节。
请记住,Blend 和免费的 Blend SDK 对绑定和行为提供了非常强大的支持。你可以观看我的视频《Silverlight 中使用 MEF 的 MVVM》,了解即使没有现有框架,实现 MVVM 模式是多么容易。文章《Silverlight 3 中使用 MEF 而非 Prism》展示了如何构建自己的命令对象。
在这个例子中,我创建了一个基类来处理属性更改事件
namespace MVVMExample
{
public abstract class BaseINPC : INotifyPropertyChanged
{
protected void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
我还实现了一个命令。通常,你会有一个更通用的命令类型来处理不同的情况,但为了演示,我只是创建了一个特定于其功能的删除命令。我使用消息框来确认删除。如果你需要更优雅的东西,例如 `ChildWindow`,请阅读我下面描述的场景,以更好地理解如何将对话框作为服务集成到 MVVM 中。
namespace MVVMExample
{
public class DeleteCommand : ICommand
{
private readonly IService _service;
private readonly Func<bool> _canExecute;
private readonly Action<ContactModel> _deleted;
public DeleteCommand(IService service, Func<bool> canExecute,
Action<ContactModel> deleted)
{
_service = service;
_canExecute = canExecute;
_deleted = deleted;
}
public bool CanExecute(object parameter)
{
return _canExecute();
}
public void Execute(object parameter)
{
if (CanExecute(parameter))
{
var contact = parameter as ContactModel;
if (contact != null)
{
var result = MessageBox.Show(
"Are you sure you wish to delete the contact?",
"Confirm Delete", MessageBoxButton.OKCancel);
if (result.Equals(MessageBoxResult.OK))
{
_service.DeleteContact(contact);
if (_deleted != null)
{
_deleted(contact);
}
}
}
}
}
public void RaiseCanExecuteChanged()
{
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged;
}
}
这个特定的命令使用一个委托在完成时进行回调,但这只允许一个订阅者。如果存在多个命令消费者(或视图模型),则需要一个多播委托或事件。
在 Silverlight 4 中,我可以直接使用 `Command` 标签将按钮绑定到命令。我在 Silverlight 3 中构建了示例,它不具备原生支持。为了创建绑定,我做了一个简单的触发器——同样,特定于这个项目,并且是为了演示——来调用命令,这样我就可以在 XAML 中轻松地绑定它。
对于我在此提供的示例,你可以查看示例应用程序。它非常简单,只包含一个服务和一个带有“模拟数据库”的视图模型。两个视图绑定到同一个视图模型,你可以单击一个联系人以查看其详细信息。你还可以删除一个联系人。
我经常收到抱怨,说博客示例过于简单。这足以展示一个完整的应用程序而无需依赖其他框架,但肯定没有展示多个页面和视图类型。我没有经常展示这些的原因很简单,因为我是一名顾问和承包商,所以我一直在为客户构建这些业务线框架和应用程序,并且无权共享他们的代码。虽然我可以为帖子构建小型示例,但我根本没有时间构建一个更大的工作模型。这是我肯定想做的事情,但对于这篇文章的发布时间来说并不实用。
你可以在这里下载此示例的源代码
我认为学习模式最简单的方法是查看一个完整的应用程序的构建过程。我在《Silverlight 中使用 MEF 的 MVVM》中演示了这一点。在该视频中,我构建了一些简单的视图模型,并展示了一个根据用户选择动态切换的视图,并使用 MEF 连接所有内容。然后在《第二部分》中引入了一个更复杂的场景。
那么那些更复杂的业务线解决方案呢……那些实际上有不止一个按钮、多个视图和复杂逻辑的解决方案呢?这超出了本文详细介绍的范围,但我想探讨几个常见场景以及我是如何用 MVVM 解决它们的。
常见 MVVM 场景
根据我的经验,绑定命令、模型或属性的想法是直截了当的。当遇到特定情况,例如显示对话框或触发动画时,MVVM 可能会让人感到困惑。我们如何解决这些常见问题?
带选择的列表
如何使用 MVVM 处理用于选择单个或多个项目的组合框?这实际上相当简单。事实上,想象一个场景,你有一个包含联系人姓名列表的组合框,同一页面上另一个视图在选择时显示联系人详细信息。ViewModel 看起来像这样,假设我正在使用 MEF 来连接依赖项(以向你展示与参考应用程序不同的方法)
public class ContactViewModel : BaseViewModel,
IPartImportsSatisfiedNotification
{
[Import]
public IContactService Service { get; set; }
public ContactViewModel()
{
Contacts = new ObservableCollection<Contact>();
}
public ObservableCollection<Contact> Contacts { get; set; }
private Contact _currentContact;
public Contact CurrentContact
{
get { return _currentContact; }
set
{
_currentContact = value;
RaisePropertyChanged("CurrentContact");
}
}
public void OnImportsSatisfied()
{
Service.FetchContacts(list =>
{
foreach(var contact in list)
{
Contacts.Add(contact);
}
CurrentContact = Contacts[0];
});
}
}
在这种情况下,我们导入一个用于获取联系人的服务,连接列表,并设置当前联系人。下拉列表绑定到 `Contacts` 集合。然而,重要的是选定项也绑定了(这就是视图模型维护状态的地方)。绑定将如下所示
...
<ComboBox ItemsSource="{Binding Contacts}"
SelectedItem="{Binding CurrentContact,Mode=TwoWay}"/>
...
这确保了只要在列表中选择了某个内容,当前联系人就会更新。还记得我提到过多个视图可能共享同一个视图模型吗?在这种情况下,联系人详细信息的视图可以使用这个相同的视图模型,并且只需绑定到 `CurrentContact` 属性。
导航
导航是一个常见的难题。如何在 MVVM 应用程序中管理导航?大多数示例只显示屏幕上的一个按钮或小部件,而不涉及具有多个页面的复合应用程序。
简短的回答是,无论你如何导航(无论你是使用自己的引擎来拉取视图,还是使用 Silverlight 提供的导航框架,或者使用 Prism 的区域管理,或者所有这些方法的组合),你都应该将机制抽象到接口后面。通过定义 `INavigation` 或类似的东西,导航不再是 MVVM 问题。无论你如何解决它,你的视图模型都可以导入 `INavigation` 并简单地导航到或根据需要触发转换。
我关于MEF 而非 Prism 的文章展示了如何使用托管可扩展性框架来完成此操作。使用 Fluent 接口自动发现视图涵盖了视图到区域的映射,而使用 Prism 动态模块加载则提供了使用导航框架的完整解决方案。
动态模块
这紧随导航之后。如果你的应用程序非常大怎么办?一次性加载所有内容通常没有意义。你希望主菜单和屏幕出现,然后根据需要动态加载其他模块。这减少了应用程序启动和运行的初始时间,也尊重了用户的浏览器和/或桌面内存和 CPU。
动态模块的问题并非 MVVM 独有,但视图模型之间以及跨模块的消息传递当然很重要。对于这些问题,我认为查看 MEF 和 Prism 等现有框架以解决具体问题更有意义。Prism 具有可以“按需”加载的模块,MEF 提供了允许动态加载 XAP 文件的部署目录。Prism 解决应用程序内消息传递的方案是事件聚合器。我在附录中会更详细地讨论这些类型问题的框架和解决方案,届时我将介绍可“开箱即用”的现有框架。
对话框
一个常见的 UI 模式是对话框(类似于消息框,但期望回复)。我看到有些人对如何使用 MVVM 和 Silverlight 的所有代码必须是异步的限制来实现这一点感到困惑。
在我看来,最简单的解决方案是将对话框抽象到接口后面,并为响应提供回调。视图模型可以导入对话框,然后根据状态的某些更改或命令触发对话框服务。回调将返回响应,然后视图可以相应地处理。
有关更详细的解释,请阅读Silverlight 中的简单对话框服务。
动画
这是一个非常常见的问题:UI 或后端触发的更改如何启动动画和其他过渡?
这个问题有几种解决方案。以下是一些解决问题的方法
- 视觉状态聚合器允许你根据 UI 事件绑定动画,而无需涉及视图模型。当你确实需要涉及它时,可以像这样
- 动画委托可以解决问题。想要更抽象的东西吗?试试
- nRoute 的反向 ICommand 实现,或者
- 使用视觉状态管理器的 MVVM 示例框架。
配置或全局值
我经常遇到的另一个问题是如何处理全局变量和配置信息。同样,这更多是一个通用架构考虑,而不是 MVVM 问题。在大多数情况下,你可以通过接口 (`IConfiguration`) 公开配置,然后使用你的配置值连接实现。任何需要信息的视图模型只需导入实现,无论是通过 MEF、Unity 还是其他机制,并且只保留一个类的副本(单例模式,尽管很可能由容器而不是类本身管理)。
异步进程
Silverlight 的一个令人困惑之处在于它强制服务调用必须是异步的。这在构建视图模型时可能显得很奇怪:何时触发调用,以及如何知道它已完成?通常,这通过在进程完成时注册到事件并绑定结果来管理。我更喜欢将事件的实现细节隐藏在简单的 `Action` 函数后面。请阅读使用 Action 简化 Silverlight 中的异步调用以获取示例。
有时你可能有一个更复杂的工作流,需要在继续之前执行并完成多个异步调用。如果出现这种情况,你可能需要研究一种机制,使其易于编码和读取顺序工作流。这篇文章将帮助你理解一种使用协程的解决方案,而这篇文章描述了如何使用现有的强大框架来管理那些具有线程安全、错误处理等的调用。
大数据集
最后,我听说有人声称 MVVM 无法很好地处理大型数据集。我会争辩说,这是某些实现存在这个问题,而不是模式本身。解决方案通常是分页数据,但我发现许多人错误地处理了这个问题。出于某种原因,开发人员坚持分页是数据库的功能,应该隔离到数据访问层。你拥有一个带有“当前页”和“总页数”的 UI 元素这一简单事实表明,它不仅仅是数据库的一个产物,而是参与到应用程序的所有层中,应该这样管理。
在最原始的形式中,你可以创建一个集合,该集合会随着用户分页而增长。如果你的数据很小,你可能会拉取一个非常大的集合并将其保留在 Silverlight 客户端中,但使用虚拟化面板来显示信息(某些面板的问题在于它们为每个绑定的数据元素创建一个控件,这会严重影响性能——虚拟化面板只创建足够的控件来填充屏幕上的可见窗口)。
像 WCF RIA 这样的技术支持 LINQ 查询。这些查询包含扩展方法,允许你只获取列表中的前几项,而不是一次性获取整个列表。该框架还提供了辅助类,如 `PagedCollectionView`,以帮助筛选、排序和分页数据。
MVVM 不是什么
除非我们谈论 MVVM 不是什么,否则任何讨论都是不完整的。
MVVM 不是一个完整的框架。它是一种模式,可能是框架的一部分,但它只是应用程序架构整体解决方案的一部分。它不涉及,也不关心,你的服务器上发生什么或者你的服务是如何组合在一起的。它确实强调关注点分离,这很好。
我敢打赌,在这篇文章中,你没有读到任何一条规则,声明“使用 MVVM,不允许使用代码隐藏”。这是一场激烈的争论,但模式本身并没有告诉你如何实现你的视图,无论是使用 XAML、代码隐藏还是两者的组合。我会建议,如果你花费数天时间编写代码只是为了避免几分钟的代码隐藏,那么你的方法是错误的。
它不是 Silverlight 或 WPF 的必需品。我相信业务线、数据驱动和基于表单的应用程序是 MVVM 的主要候选者。游戏、娱乐网站、绘图程序等可能没有意义。请确保你为正确的工作使用正确的工具。
MVVM 不应该让你放慢速度!所有新的模式和框架都有学习曲线。你必须接受你的开发人员需要学习和理解这种模式,但你不应该接受你的整个流程突然变得更长或被延迟。当它加速开发、提高稳定性和性能、降低风险等等时,这种模式才有用。当它减缓开发、引入问题,并让你的开发人员每次听到“设计模式”这个词时都感到畏缩时,你可能需要重新考虑你的方法。
结论
好了,我们完成了!就是这样。我希望你已经了解了 MVVM 对 Silverlight 和 WPF 应用程序如此强大的原因,这种模式的样子,甚至解决了 MVVM 可以解决的常见问题的示例。
附录 A:一些历史模式
模型-视图-控制器 (MVC)
这种软件架构模式最初于 1979 年在 Xerox 的 Smalltalk 环境中被描述。如果你感兴趣,可以通过点击此处(PDF)下载一些原始论文(PDF 格式)。
模型-视图-演示器 (MVP)
1996年,Model-View-Presenter 模式 (PDF) 被引入世界。这种模式建立在 MVC 的基础上,但对控制器(现在称为演示器)施加了特殊的约束。一个概述看起来像这样
Martin Fowler 描述了这种模式的两种风格:主管控制器/演示器和被动视图。微软这样描述它:MVP。
演示模型
2004 年,Martin Fowler 发表了他对演示模型的描述。摘要非常简洁:“独立于界面中使用的 GUI 控件来表示演示的状态和行为。”正如你所看到的,MVVM 是这种模式的一种特殊形式
附录 B:现有 MVVM 框架
既然我们对 MVVM 有了初步了解,你就不必重新发明轮子了。现在有许多开箱即用的框架实现了 MVVM。排名不分先后
这是一个非常流行的工具包,开箱即用地支持基本视图模型、命令、消息传递和项目模板,以帮助你入门。它同时支持 WPF 和 Silverlight 项目。
此框架的既定目标是实现用户界面的简洁声明性规范、更容易分离视图和代码,并提供一个精简的框架。
Caliburn 是一个流行的视图模型优先框架,支持 WPF 和 Silverlight。然而,它不仅仅是 MVVM,它是一个完整的应用程序框架。
Cinch 是一个功能齐全的 WPF MVVM 框架,可简化富 MVVM WPF 应用程序的开发。它还提供 UI 服务/线程/单元测试帮助程序等。
另一个 MVVM 框架,这个库的独特之处在于“反向命令”,它允许将命令绑定到视图中的事件,而不是让视图简单地向视图模型发送命令。
一种非常精简和轻量级的 MVVM 方法。
尽管名称如此,该框架同时支持 WPF 和 Silverlight。它提供了参考 MVVM 实现和快速入门,以及对组合复杂应用程序的支持和指导,包括命令、事件聚合、区域管理等。
虽然这肯定不是一份详尽的列表,但希望它能让你了解 MVVM 可用的开源社区支持,以及你可以选择现有的成熟框架来加速开发。