我们总会有这样一个经验:一个系统最不容易也最不应该变化的部分是领域逻辑,最容易变化也最应该变化的是数据的呈现方式。
在java的各种应用中可以说是到处可见mvc,j2ee贯穿mvc的概念,android的开发方式也是类mvc的,mvc结构对于做过java应用的人而言简直就是司空见惯。而在.net这边,由于之前微软为大家提供的各种winform、asp.net项目典范(比如那个petshop series)将“三层”概念很好的灌输到了.net程序员的大脑中,许多.net开发者凡是做个东西都要搬出自己最拿手的IModel、IDAL这样的神器。
其实mvc与所谓的“三层架构”是两个层次上的东西,前者是一种结构模式,而后者则是分层的角度去说。
一件很奇怪的事情,许多人知道“三层”却不知道mvc,其实这要归结与.net的早期开发技术asp.net和winform这些page controller的典范让许多人对三层夸夸其谈却对mvc视而不见甚至一无所知。什么是page controller模式呢?搞.net的大多都用过winform和webform,这种xxxform用起来很直观,我们想要做一个程序,ok,最简单的方式就是拖拖拽拽几个控件,然后在一个叫code behind的东西里写这些UI事件的处理逻辑,加一大堆变量用于记录数据和状态,这样一个程序就能出炉。这种开发方式对于一些小软件系统的开发其实效率还是蛮高的,后来人们看到其弊端---一旦修改UI,事件处理就要跟着变,但是业务还是那个业务,凭什么要修改非UI的代码?于是有人提出“三层”,最朴素的理解就是将原本那堆事件处理里的code分成业务代码和数据库访问代码并转移到其它类中,做多了就把那坨UI叫做UI,那坨业务代码叫做BLL,那坨DAO叫做DAL。也就是这种架构:
而对于j2ee的开发者来说熟悉的是下图。
(说明:这两幅图copy自是daxnet文)
MVC是什么
MVC是一个很经典的结构,并且其又其思想衍生出很多变种比如MVP,MVVP。传统的MVC结构之一是这样的(拿主动型mvc来说):
比如web开发(比如asp.net mvc或者是java的web开发方式),view就是纯web页面或者webservice,当提交一个表单/调用webservice或者ajax后会将数据提交给controller(当然期间可能会经过各种filterchain、listener这样的东西)controller调用相应的业务模块来处理这个请求,最终结果会更新View的显示。
MVP
对于非天然mvc的框架
对于asp.net/winform而言,虽然可以通过改造让其支持mvc结构的开发(比如通过定制IHttpModule、IHttpHandler云云),但是在企业看来这些都算是邪门武功(因为这样会丧失xxxform在开发上的很多特性比如快速开发)。大多数使用的是mvp模式。什么是mvp呢?其实mvp是mvc的一个变种。因为用winform或者webform的话form始终是个阻碍mvc开发的问题。那么好,我们仍然使用designer和codebehind,其实一个架构设计的好坏是取决于人而不是具体的技术的,只要我们OO一时强page controller一样好用。
在MVP模式中我们需要自己定制各个View(web页面或者窗体)对应的IView和IPresenter、IModel。IView要对IPresenter暴露操作UI、数据绑定的接口,IPresenter对IView要暴露当UI事件触发需要调用的接口,IPresenter根据IView传递过来的请求调用业务接口并根据结果操作UI。举个简单的例子,一个计算“x+y=?”的程序。如果我们这样定义IPresenter和IView
public interface IPresenter
{
IView View { get; set; }
void CalculateResult();
}
public interface IView
{
IPresenter Presenter { get; set; }
void ShowResult(string result);
int ValueOne { get; }
int ValueTwo { get; }
}
IPresenter的实现如下(这里从简把IModel去掉了)
Presenter
namespace
ClientLibrary
{
public
class
Presenter : IPresenter
{
private
IView _view;
public
IView View
{
get
{
return
_view;
}
set
{
_view
=
value;
_view.Presenter
=
this
;
}
}
private
static
readonly
string
RESULT_FORMATTER
=
"
{0}+{1},the result is {2}
"
;
public
void
CalculateResult()
{
if
(_view
!=
null
)
{
var result
=
string
.Format(RESULT_FORMATTER, _view.ValueOne, _view.ValueTwo, _view.ValueOne
+
_view.ValueTwo);
_view.ShowResult(result);
this
.A
=
123
;
}
}
private
int
_a;
public
int
A
{
set
{
A
=
value;
}
}
}
}
View的实现如下(那silverlight为例,换成别的也行)
MainPage
namespace
debug
{
public
partial
class
MainPage : UserControl, IView
{
public
MainPage()
{
InitializeComponent();
}
private
IPresenter _presenter;
private
void
btn_Click(
object
sender, RoutedEventArgs e)
{
if
(_presenter
!=
null
)
{
_presenter.CalculateResult();
}
#region
hidden
/*
int total = 0;
try
{
total = int.Parse(tb1.Text) + int.Parse(tb2.Text);
MessageBox.Show("计算结果:" + total.ToString());
}
catch (Exception ex)
{
MessageBox.Show("出错啦" + ex.ToString());
}
finally
{
tb1.Text = string.Empty;
tb2.Text = string.Empty;
}
*/
#endregion
}
public
IPresenter Presenter
{
get
{
return
_presenter;
}
set
{
_presenter
=
value;
}
}
public
void
ShowResult(
string
result)
{
MessageBox.Show(result);
}
public
int
ValueOne
{
get
{
return
int
.Parse(tb1.Text); }
}
public
int
ValueTwo
{
get
{
return
int
.Parse(tb2.Text); }
}
}
}
一个很简单的东西,看上去写成的要多些那么一坨东西,但是好处是显而易见的,就是更换view非常方便,根本不用去改你的IPresenter、Presenter和业务。一切都是接口调用而不依赖具体实现,这就是好处。
你必须要懂的MVVM
对于.NET平台的开发人员,托微软的福分我们拥有一种更为强大的模型---MVVM。这应该算是做WPF/Silverlight应用的人必懂的一种结构,WPF/silverlight天生支持数据绑定和命令绑定(不过sl在命令绑定上还比较弱),这就为我们使用MVVM创造了可能。
View是什么呢,纯的View只有xaml或者附带必要的只与View本身相关逻辑代码。ViewModel,你可以把它理解为View具体呈现内容所依赖数据的一个抽象,在MVVM中View与ViewModel总会有一种绑定关系,一旦ViewModel中被绑定的数据发生改变View上的数据就会跟着变,相反也有可能,比如你的账号密码框内容发生变化,关联的ViewModel中的数据就会被框架自动通知到。
在wpf/silverlight中,绑定是通过xaml语法来完成(虽然你可以选择用c#来写但不符合mvvm的宗旨),并且绑定双方的通知机制是有框架来完成,也就是说一个会xaml和blend的美工只需事先和coder商量下“咱们的xx和xx是在哪个ViewModel上叫XXX的属性的XXX属性……”问题之后就可以各干各的了。那么ViewModel怎么写,咋view中又怎么绑定到viewmodel呢?首先我们谈ViewModel。
说道ViewModel你需要知道依赖属性和依赖对象的概念,这是wpf/silverlight的基础所以不多说。有两种方式写ViewModel。第一种是自己去实现INotifyPropertyChanged接口,并在属性变化时去调用NotifyPropertyChanged事件。
为了方便我们定义一个ViewModelBase的抽象基类,然后让其他ViewModel继承这个基类。
ViewModelBase
public
abstract
class
ViewModelBase : System.ComponentModel.INotifyPropertyChanged, IDisposable
{
public
event
System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
protected
void
OnPropertyChanged(
string
propertyName)
{
if
(PropertyChanged
!=
null
)
{
var arg
=
new
System.ComponentModel.PropertyChangedEventArgs(propertyName);
PropertyChanged(
this
, arg);
}
}
public
virtual
void
Dispose()
{
}
}
DemoViewModel
public
class
DemoViewModel : ViewModelBase
{
#region
fields
private
string
_propertyA;
#endregion
#region
presentation properties
public
string
PropertyA
{
get
{
return
_propertyA;
}
set
{
if
(_propertyA
!=
value)
{
_propertyA
=
value;
base
.OnPropertyChanged(
"
PropertyA
"
);
}
}
}
#endregion
}
第二种是利用DependencyObject和DependencyProperty。
PeopleItemViewModel
public
class
PeopleItemViewModel : DependencyObject, IPeopleItemViewModel
{
public
PeopleItemViewModel()
{
}
public
static
readonly
DependencyProperty SimpleUserDataProperty
=
DependencyProperty.Register(
"
SimpleUserData
"
,
typeof
(SimpleUserData),
typeof
(PeopleItemViewModel));
public
static
readonly
DependencyProperty RelativeSimpleUserDataProperty
=
DependencyProperty.Register(
"
RelativeSimpleUserData
"
,
typeof
(ObservableCollection
<
SimpleUserData
>
),
typeof
(PeopleItemViewModel));
public
static
readonly
DependencyProperty AllSimpleUserDataProperty
=
DependencyProperty.Register(
"
AllSimpleUserData
"
,
typeof
(ObservableCollection
<
SimpleUserData
>
),
typeof
(PeopleItemViewModel));
public
SimpleUserData SimpleUserData
{
get
{
return
(SimpleUserData)
base
.GetValue(SimpleUserDataProperty);
}
set
{
if
(
!
base
.CheckAccess())
{
Dispatcher.Invoke(
new
Action(
()
=>
{
SimpleUserData
=
value;
}));
}
else
base
.SetValue(SimpleUserDataProperty, value);
}
}
public
ObservableCollection
<
SimpleUserData
>
RelativeSimpleUserData
{
get
{
return
(ObservableCollection
<
SimpleUserData
>
)
base
.GetValue(RelativeSimpleUserDataProperty);
}
set
{
if
(
!
base
.CheckAccess())
{
Dispatcher.Invoke(
new
Action(
()
=>
{
RelativeSimpleUserData
=
value;
}));
}
else
{
base
.SetValue(RelativeSimpleUserDataProperty, value);
var collectionView
=
CollectionViewSource.GetDefaultView(value);
collectionView.SortDescriptions.Add(
new
SortDescription(
"
Distance
"
, ListSortDirection.Ascending));
}
}
}
public
ObservableCollection
<
SimpleUserData
>
AllSimpleUserData
{
get
{
return
(ObservableCollection
<
SimpleUserData
>
)
base
.GetValue(AllSimpleUserDataProperty);
}
set
{
if
(
!
base
.CheckAccess())
{
Dispatcher.Invoke(
new
Action(
()
=>
{
AllSimpleUserData
=
value;
}));
}
else
{
base
.SetValue(AllSimpleUserDataProperty, value);
var collectionView
=
CollectionViewSource.GetDefaultView(value);
collectionView.SortDescriptions.Add(
new
SortDescription(
"
Distance
"
, ListSortDirection.Ascending));
}
}
}
}
在View中绑定ViewModel。
为了方便,我们可以在app.xaml中将需要的viewmode放到全局资源字典中。
然后再我们的vs视图设计器Properties(中文版显示的是“属性”)页上选择为绑定源设置绑定目标(包括source和path等)以及必要的值转换器等等即可。
(PS:虽然vs很强大,但个人还是建议熟悉xaml的绑定语法,想当初用vs2008搞wpf的时候貌似还没有这么方便的设计器。。。)