MVVM 之解

fuzhimei 2014-01-18 10:35:05

问题引入
1 场景一:团队辛辛苦苦完成了一个项目,抱着激动的心情去给用户做demo,而用户给你的反馈是UI很不满意,要重新修改,否则拒绝验收。大规模修改UI,晴天霹雳!
2 场景二:产品在一家客户上线运行反应不错,公司准备扩大营销市场,寻求更多的客户,此时,不同客户对UI纷纷提出修改意见,众口难调,但是老总发话,客户是上帝!
问题出来了,按照传统的开发模式是基于CodeBehind这样的方式,UI总是和业务逻辑紧密耦合在一起, UI修改,无法避免的业务逻辑修改随之而来,这无非就是我们老生常谈的解耦问题,有没有办法做到UI层剥离出逻辑层呢?MVVM模式为你排忧解难。

一 什么是MVVM模式
MVVM(Model-View-ViewModel)是专为WPF和SilverLight设计的开发模式, 与之类似的有Asp.net程序对应的MVC模式, WinForm程序对应的MVP, 关于MVC, MVP此处不展开论述,详情参考http://msdn.microsoft.com/zh-cn/library/dd381412(v=vs.98).aspx。 但在MVC和MVP模式中, View层都具有很多代码逻辑, 最简单的例子是在MVC中当界面发生交互时View去调用Controler中的某个方法,所以 并没有真正意义上实现View与ViewModel完全分离。
WPF真正引人入胜、使之与WinForm泾渭分明的特点就是——“数据驱动界面”,何为“数据驱动界面” , 与传统的“事件驱动见面”相比较,数据编程了核心,UI处于从属地位;数据是底层、是心脏,数据变了作为表层的UI就会跟着变、将数据展现给用户;如果用户修改了UI元素上的值,相当于透过UI元素直接修改了底层的数据;围绕着这个核心,WPF准备了很多概念相当前卫的技术,其中包括为界面准备的XAML、为底层数据准备的Dependency Property & Binding和为消息传递准备的Routed Event & Command。
Binding和Command技术的出现,也为MVVM模式成为WPF平台下一个优秀的开发模式奠定了基础。通过Binding,可以绑定一个View的Property到ViewModel, ViewModel 对象被设置为视图的 DataContex,如果属性值在 ViewModel 更改,这些新值自动传播到通过数据绑定的视图,实现在ViewModel里可以不通过编写任何逻辑代码就直接更新View,做到View与ViewModel之间的完全松耦合,关于Binding,想了解更多可参见 Data and WPF: Customize Data Display with Data Binding and WPF "。 同样,如果没有WPF中的Command, MVVM也很难展示出它的强大力量,ViewModel可将Command暴露给View, 使得View可以消费command中对应的逻辑功能,对于不熟悉command的朋友,可以参考这篇文章Advanced WPF: Understanding Routed Events and Commands in WPF。
下面简要介绍一下MVVM每个模块的主要职责
1) View主要用于界面呈现,与用户输入设备进行交互,在code-Behind中还可以些一些UI的逻辑的,比如一些丰富的动画效果,或者直接设置某个元素的样式等,此外,设置View层的DataContext为对于的ViewModel层的逻辑也是写在code-Behind中。
2) ViewModel是MVVM架构中最重要的部分,ViewModel中包含属性,命令,方法,事件,属性验证等逻辑,用于逻辑实现,负责View与Model之间的通信。
3) Model就是我们常说的数据模型,用于数据的构造,数据驱动, 主要提供基础实体的属性以及每个属性的验证逻辑。
MVVM中各个模块的交互方式如图所示:


二 为什么要使用MVVM模式
MVVM模式的引入能给我们带来什么优势呢?相信这是大多数学习MVVM的人关心的一个主要问题。
首先我们应该清楚地认识到,MVVM不是适用于任何的项目开发,一个项目是否要上一套框架取决于项目本身的规模和性质,盲目的使用开发模式可能会引起过度开发,通常情况下,企业级的WPF应用软件建议使用,主要优势下面将展开详细阐述。
1团队层面 统一了项目团队的思维方式,也改变了开发方式,由于View与ViewModel之间的松耦合关系,我们可以轻易做到开发团队与设计团队的明确分工,开发团队可以专注于创建功能强大的 ViewModel 类,而设计团队能够熟练运用Blend等工具能为程序员输出用户友好的试图View的XAML文件。而且,随着项目的进行,不断会有新的成员加入,一个清晰的项目设计模式,能够很大程度地减少他熟悉项目的所需时间,并能够规范的进行接下来的开发维护工作。
2 架构层面 项目架构更加稳定,模块之间松散的耦合关系使得模块之间的相互依赖性大大降低,这也就意味着项目的扩展性得到了提高,即使以后需要加一些新的模块,或者实现模块的注入,我们也能做到最小的改动,从而保证项目的稳定。
3 代码层面MVVM的引入也使得项目本身变得模块清晰化,条理化,有助于我们更好地区分哪些逻辑是属于UI操作,哪些逻辑是业务操作,增强了代码的可读性、可测性。对于ViewModel层,Views和Unit tests是两个不同类型的消费者,应用程序中的主要交互逻辑处于ViewModel层,这样,在完成ViewModel之后,我们完全可以有理由相信,我们可以对ViewModel进行单元测试,因为它不依赖于任何UI控件,从这个角度看,似乎UnitTest相比于View而言具备更大的消费能力。

三 详解ViewModel
 ViewModel是MVVM架构中最重要的部分,负责View与Model直接的通信,对于ViewModel的理解是掌握MVVM的关键,下面我们针对ViewModel进行详细剖析。
1 ViewModel的属性ViewModel的属性是View数据的来源,但ViewModel层不能是Model层的简单封装,ViewModel层也不能是View层的简单映射。ViewModel的属性可由三部分组成:一部分是Model的复制属性;另一部分用于控制UI状态。例如Button属性的Disable属性,当操作完成时可以通过这个属性更改通知View做相应的UI变换或者后面提到的事件通知;第三部分是一些方法的参数,可以将这些方法的参数设置成相应的属性绑定到View中的某个控件,然后在执行方法的时候获取这些属性,所以一般方法不含参数。
2 ViewModel的命令 ViewModel中的命令用于接受View的用户输入,并做相应的处理。我们也可以通过方法实现相同的功能。
3 ViewModel的事件 ViewModel中的事件主要用来通知View做相应的UI变换。它一般在一个处理完成之后触发,随后需要View做出相应的非业务的操作。所以一般ViewModel中的事件的订阅者只是View,除非其他自定义的非View类之间的交互。
4 View及ViewModel交互模式
在View与ViewModel模型之间进行双向的联系的主要方式是通过数据绑定。当正确地使用该设计模式后,每一个View除了纯净的XAML和非常少量的后置代码外不会再包含任何东西,彻底地做到了界面展示和业务逻辑的分离,让程序员更加专注于代码的编写。
ViewModel也能用来容纳View的状态以及执行View需要的任何命令。
因为WPF内置了Command模式,对于像Button控件之类的UI元素来说都有一个Command的属性,它是WPF所定义的ICommand类型。可以把这些命令放到ViewModel中并以公有属性的形式暴露出来,这样就可以让View对其进行绑定。这极其强大,因为它可以把ModelView中的可执行代码绑定到窗体的Button上。

四 MVVM实践
理论知识已经准备充分,现在是检验真理的时刻,以下是使用了Model-View-ViewModel 设计模式的s世上最简单的WPF应用程序例子,简单加法计算器。
1 代码结构如下图:


2 CaculatorModel类:
public class CaculatorModel
{
public int Num1 { get; set; }
public int Num2 { get; set; }
public int Result { get; set; }
}

3 ICommand类型的基类DelegateCommand

using System;
using System.Windows.Input;
namespace MVVMDemo.Commands
{
public class DelegateCommand:ICommand
{
public DelegateCommand(Action<object> executeCommand, Func<object, bool> canExecuteCommand)
{
this.executeCommand = executeCommand;
this.canExecuteCommand = canExecuteCommand;
}
// The specific ExecuteCommand aciton will come from the ViewModel, the same as CanExecuteCommand
private Action<object> executeCommand;

public Action<object> ExecuteCommand
{
get { return executeCommand; }
set { executeCommand = value; }
}

private Func<object, bool> canExecuteCommand;

public Func<object, bool> CanExecuteCommand
{
get { return canExecuteCommand; }
set { canExecuteCommand = value; }
}

public event EventHandler CanExecuteChanged;

public bool CanExecute(object parameter)
{
if (CanExecuteCommand != null)
{
return this.CanExecuteCommand(parameter);
}
else
{
return true;
}
}

public void Execute(object parameter)
{
if (this.ExecuteCommand != null) this.ExecuteCommand(parameter);
}

public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
注:ICommand中有两个方法CanExecute和Execute必须实现,这两个方法分别对应着当Command调用时判断是否能执行和具体执行逻辑。

4 ViewModelBase类

using System.ComponentModel;

namespace MVVM.ViewModel
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
注:ViewModelBase实现了接口INotifyPropertyChanged, 在该接口中有一个PropertyChanged事件, 当ViewModel中的Property改变时,允许触发PropertyChanged事件,继而重新绑定数据到UI上。
5 CaculatorViewModel类
using System.Windows.Input;
using MVVM.Model;
using MVVMDemo.Commands;

namespace MVVM.ViewModel
{
public class CaculatorViewModel:ViewModelBase
{
#region Fields

private int num1;
private int num2;
private int result;
private CaculatorModel model;

#endregion

#region Properties

public int Num1
{
get
{
return num1;
}
set
{
num1 = value;
this.RaisePropertyChanged("Num1");
}
}

public int Num2
{
get
{
return num2;
}
set
{
num2 = value;
this.RaisePropertyChanged("Num2");
}
}

public int Result
{
get
{
return result;
}
set
{
result = value;
this.RaisePropertyChanged("Result");
}
}

#endregion

#region Commands

public ICommand CaculateCommand{get;set;}
public ICommand ClearCommand { get; set; }

#endregion

#region Methods

public void Add(object param)
{
Result = Num1 + Num2;
}

public void Clear(object param)
{
Result = 0;
Num1 = 0;
Num2 = 0;
}

public void InitilizeModelData()
{
// In gernal, the data comes from database
var model = new CaculatorModel()
{
Num1 = 1,
Num2 = 1,
Result = 2
};

Num1 = model.Num1;
Num2 = model.Num2;
Result = model.Result;
}

public CaculatorViewModel()
{
CaculateCommand = new DelegateCommand(Add, null);
ClearCommand = new DelegateCommand(Clear, null);

InitilizeModelData();
}

#endregion
}
}
6 简单计算器的UI



该View所对应的XAML文件如下:
<Window x:Class="MVVM.View.CaculatorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CaculatorView" Height="300" Width="682">
<Grid Width="596">
<TextBox Height="23" HorizontalAlignment="Left" Margin="41,90,0,0" Name="txtNum1" VerticalAlignment="Top" Width="120" Text="{Binding Num1}"/>
<TextBox Height="25" HorizontalAlignment="Left" Margin="195,88,0,0" Name="txtNum2" VerticalAlignment="Top" Width="120" Text="{Binding Num2}"/>
<Label Content="+" Height="28" HorizontalAlignment="Left" Margin="167,88,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="25" HorizontalAlignment="Left" Margin="364,88,0,0" Name="textBox5" VerticalAlignment="Top" Width="120" Text="{Binding Result}"/>
<Button Content"=" Height="23" HorizontalAlignment="Left" Margin="328,90,0,0" Name="button1" VerticalAlignment="Top" Width="28" Command="{Binding CaculateCommand}" />
<Button Content="Clear" Height="26" HorizontalAlignment="Left" Margin="501,88,0,0" Name="button2" VerticalAlignment="Top" Width="45" Command="{Binding ClearCommand}" />
</Grid>
</Window>
6. View的Code-Behind
using System.Windows;
using MVVM.ViewModel;

namespace MVVM.View
{
/// <summary>
/// CaculatorView.xaml 的Ì?交?互£¤逻?辑-
/// </summary>
public partial class CaculatorView : Window
{
public CaculatorView()
{
InitializeComponent();
this.DataContext = new CaculatorViewModel();
}
}
}
注:这里讲View的DataContext设为CaculatorViewModel实例,至此,View和ViewModel建立关联。

...全文
11547 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
rrats 2015-08-25
  • 打赏
  • 举报
回复
引用 5 楼 sp1234 的回复:
同时,如果你在一个层次很低的开发团队中,你可能身边的一帮人都有一种“像壳一样坚硬”的私心,他们生怕你设计出了高级的架构,而他们没有人能够理解、能够接手。 MVVM就是这样,会让一些人吓得发抖,然后各种怪话连篇地出现。 而写技术文章时,容易对应用说的过少而技术说的实在是过多。
原来是这么回事啊,我还以为高级的架构是帮助码农更容易理解和接手呢,看来真是2羊2奶义务。
wwwang89123 2014-09-25
  • 打赏
  • 举报
回复
好东西
wanghui0380 2014-01-19
  • 打赏
  • 举报
回复
MVVM(Model-View-ViewModel)是专为WPF和SilverLight设计的开发模式, 与之类似的有Asp.net程序对应的MVC模式, WinForm程序对应的MVP, 关于MVC, MVP此处不展开论述,详情参考http://msdn.microsoft.com/zh-cn/library/dd381412(v=vs.98).aspx。 但在MVC和MVP模式中, View层都具有很多代码逻辑, 最简单的例子是在MVC中当界面发生交互时View去调用Controler中的某个方法,所以 并没有真正意义上实现View与ViewModel完全分离WPF真正引人入胜、使之与WinForm泾渭分明的特点就是——“数据驱动界面”,何为“数据驱动界面” , 与传统的“事件驱动见面”相比较,数据编程了核心,UI处于从属地位;数据是底层、是心脏,数据变了作为表层的UI就会跟着变、将数据展现给用户;如果用户修改了UI元素上的值,相当于透过UI元素直接修改了底层的数据;围绕着这个核心,WPF准备了很多概念相当前卫的技术,其中包括为界面准备的XAML、为底层数据准备的 基本上我红字标出滴话,没有一句是对滴。估摸着你这也是从博客园上转过来滴,唉!博客园在误人子弟这条路数算是不遗余力滴 上面老p已经说了第一这不是sl,wpf专有滴 下面我要说:asp.net也不对应mvc,winfrom同样不对应mvp 另外在说一下现在连js里面都有mvvm框架,已经响应式编程后端,这表明asp.net一样可以双向绑定一样可以数据是数据,UI是UI
  • 打赏
  • 举报
回复
同时,如果你在一个层次很低的开发团队中,你可能身边的一帮人都有一种“像壳一样坚硬”的私心,他们生怕你设计出了高级的架构,而他们没有人能够理解、能够接手。 MVVM就是这样,会让一些人吓得发抖,然后各种怪话连篇地出现。 而写技术文章时,容易对应用说的过少而技术说的实在是过多。
  • 打赏
  • 举报
回复
binding 是用了超过15年的东西了,不是新东西。 给技术上的名词儿断定年份需要谨慎。因此其实一般应该只介绍技术应用,不要轻易地说它是哪一个年份才发明的。 不过这篇文章的中心思想我是赞同的。假设同样的效果、同样的性能的两个程序,一个写了20万行代码,而另外一个只有9千行代码,我绝对会认为后者一定是非常高级的设计思想的体现(而不是诡异代码)才会造成代码精简。 因此尽可能少写代码甚至不写代码,我赞成只有这样的程序员才是好程序员。但是这样的程序员应该会开发这种组件给别人使用,因此不写代码的人不是那种整天混日子、整天忽悠别人的标题党,而是花时间进行系统设计和能够写出这类文章的。
  • 打赏
  • 举报
回复
引用 5 楼 sp1234 的回复:
同时,如果你在一个层次很低的开发团队中,你可能身边的一帮人都有一种“像壳一样坚硬”的私心,他们生怕你设计出了高级的架构,而他们没有人能够理解、能够接手。 MVVM就是这样,会让一些人吓得发抖,然后各种怪话连篇地出现。 而写技术文章时,容易对应用说的过少而技术说的实在是过多。
P哥头像模糊了
  • 打赏
  • 举报
回复
不要说WPF/Siverlight之前的开发人员不懂使用Binding,不要说 MVP 就不使用Binding,也不要说 WPF/Silverlight 就不使用MVP(实际上在wpf开发中还是大量频繁地使用mvp思路,远远大于在xaml中使用binding)
  • 打赏
  • 举报
回复
Binding不是WPF/sivlerlight的,早在Winform中就大量使用,winform中也有着非常完备的Binding机制,只不过winform的Binding不支持(也不可能支持XAML)而已。 而asp.net其实也有Binding,支持设计代码中的Eval等数据绑定。只不过是单向的而已。

13,347

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 .NET技术前瞻
社区管理员
  • .NET技术前瞻社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧