2007年3月30日星期五

Upgrade to support multi validator attribute

简介

昨 天我在“validate a control with only one line code”中介绍了如何利用属性来实现类似asp.net中的validator的功能,今天花一些时间将这一功能进行升级,使得它可以支持对一个控件使 用多种检查,类似在asp.net中可以同时给一个控件使用多个validator。

        [RegularValidator("^[0-9]$")] //attribute的尾巴可以省略
[RequiredField]
private System.Windows.Forms.TextBox textBox2;

这样检验一个textbox的值不可为空并且满足正则表达式的约束,是不是很简单也很清楚呢?


详情见我在Spaces.Live上的文章: “Upgrade to support multi validator attribute” on Spaces


blog spot好了才两天又挂掉了:(

2007年3月29日星期四

validate a control in winform

在asp.net中如果要检查页面上的一个输入框是否为空,即需要用户输入一些东西,我们可以使用RequiredFieldValidator,做一些简单的设定就可以保证我们在后台读取这个输入框的时候它的值不会为空,但是不明白为什么winform里没有这样东西。winform中虽然提供一个errorprovider但是仍然需要我们自己编写validating的代码,反反复复的写了很多检验代码,既烦琐又不容易维护。借助Attribute的特性,其实我们可以将类似的检验做的更加简单,比如:
[RequiredFieldAttribute]
public System.Windows.Forms.TextBox textBox1;
只需要在控件上加上[RequiredFieldAttribute],我们就可以让检验变得自动化。

首先我们需要一个验证用的属性Attribute,关于该属性的介绍可以参看MSDN。
public class RequiredFieldAttribute:Attribute,IDisposable
{
public RequiredFieldAttribute()
{
Validating_EventHandler = new CancelEventHandler(Validate);
}

private void Validate(object sender, CancelEventArgs e)
{
Control ctr = sender as Control;
e.Cancel = (ctr.Text == "");
}

public CancelEventHandler ValidatingDelegate
{
set{
Validating_EventHandler = value;
}
get
{
return Validating_EventHandler;
}
}
CancelEventHandler Validating_EventHandler;

#region IDisposable 成员

public void Dispose()
{
Validating_EventHandler = null;
}

#endregion
}
在定义好属性之后,就可以像前面说的那样将属性附加到我们要验证的控件上去:
[RequiredFieldAttribute]
public System.Windows.Forms.TextBox textBox1;
不过这里要求该控件的定义是“public”的,后面会讲到为什么。附加的属性不会自己起作用,除非是.net framework中默认那些,对于我们自己定义的属性CLR并不了解,所以要使自己定义的属性起作用,还需要自己做一些工作。下面我们定义一个ValidatorEngine的类:
public class ValidatorEngine
{
public ValidatorEngine(ContainerControl container)
{
this.container = (ContainerControl)container;
this.typeOfContainer = container.GetType();
}

public void Active()
{
Active(this.container);
}
private void Active(ContainerControl container)
{
//get all public fields
FieldInfo[] fields = typeOfContainer.GetFields();//1
foreach (FieldInfo fi in fields)
{
//get attributes assigned to the field
object[] attributes = fi.GetCustomAttributes(false);//2
foreach (Attribute attr in attributes)
{
RequiredFieldAttribute validator = attr as RequiredFieldAttribute;//3
if (validator != null)
{
Control[] childControls = container.Controls.Find(fi.Name, false);//4
//the number of child controls should always be "1"
if(childControls.Length>0)
//register validator defined in RequiredFieldAttribute for our controls
childControls[0].Validating += validator.ValidatingDelegate;//5
}
}
}
}
Type typeOfContainer;
ContainerControl container;
}
engine做了些什么工作?
  1. 取得容器控件(比如我们的Form)的所有公开(public)的字段。设为public的可以取到,设计器默认的private好像取不到,试验了加上BindingFlags的枚举好像也不行,如果有知道的请留言告知:)
  2. 对于一个字段,读取附加到它上面的自定义属性。参数是表示是否要搜说该字段所继承的属性,这里不需要所以设为false。
  3. 判断找到的属性是不是我们所设定的,即RequiredFieldAttribute,如果是那说明这个字段表示的控件是不许为空的。
  4. 如果控件前面有我们附加的属性,那么通过字段名找到这个控件。本例子中的控件就在容器控件的第一级子控件集合中,所以Find的第2个参数为false,不在更深的层次中搜索。
  5. 给控件注册Validating事件。例子中使用了我们在RequiredFieldAttribute类中设定的处理方法,这样可以让各种类似的控件使用相同的验证代码。如果要自定验证方法,可以通过:
    public CancelEventHandler ValidatingDelegate 属性传入,但是这样我们附加属性的代码就与前面的略微有一些不同了,需要使用到属性的命名参数,请参考MSDN:
    [RequiredFieldAttribute(ValidatingDelegate =yourValidatingHandler)]
    public System.Windows.Forms.TextBox textBox1;
最后来看一下如何使用Engine。Engine不会自己执行自己,而且他需要一个容器控件作为参数传入,于是我们可以考虑在任何的容器控件初始化之后来启动Engine,这里就以Form为例:
private void FormBase_Load(object sender, EventArgs e)
{
//give engine the reference of a ContainerControl
engine = new ValidatorEngine(this);
//start then engine ,it will process our custom attributes
engine.Active();
}
//define a engine
ValidatorEngine engine;

这个例子很简单,只有一个自定义属性,但是它已经可以让我们看到Attribute的威力了。

2007年3月28日星期三

ReadOnlyCollection :threadsafe and readonly

为了让对象是只读的可以将其标示为ReadOnly的,但是如果这样做的话我们的对象就只能在构造函数中初始化,在其他的地方没办法操作了,这往往都不是我们所希望的。有一种做法是我们让对象是可以修改的,但是当没有权限操作(或说不应该修改)它的对象获得它的引用(一定是引用,值传递没这个问题)的时候我们就返回一个对象那个的Copy,比如

public abstract class OperationBase : IOperation
{
public OperationBase(AppPart appPart)
{
this.appPart = appPart;
}

……

public AppPart AppPart
{
get
{
//protect this appPart from being modified
return (AppPart)appPart.Clone();
}
//set
//{
// appPart = value;
//}
}

……

protected AppPart appPart;

……

}

这样其实不是真正的只读(ReadOnly),只不过对“Clone对象”的修改都不会影响到“源对象”而已。对于简单的对象这样做没有问题,但是如果“大对象”比如集合,要Clone的代价就比较大了,所以我们不简单的返回Clone。我们可以自己设计一个集合让它实现IEnumerable接口,但是内部存储使用一个集合,但是这样又带来一个问题,很多集合的操作我们无法使用了,因为IEnumarable接口提供的方法太少。其实.net2.0提供了一个解决方法——“ReadOnlyCollection”,

An instance of the ReadOnlyCollection generic class is always read-only. A collection that is read-only is simply a collection with a wrapper that prevents modifying the collection; therefore, if changes are made to the underlying collection, the read-only collection reflects those changes.

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

A ReadOnlyCollection can support multiple readers concurrently, as long as the collection is not modified.
上面是MSDN的介绍,我们可以看到它不仅是只读的,而且静态实例对于“读操作还是线程安全”,前提是读的同时没有线程去修改该集合内部的集合(正真存储了数据的集合)。ReadOnlyCollection其实只是对普通结合的一个包装,将一些修改的操作屏蔽掉了。
public class ReadOnlyCollection : IList, ICollection, IEnumerable, IList, ICollection, IEnumerable
ReadOnlyCollection的定义说明它实现了像Ilsit,ICollection这样的接口,但是他们是有修改操作的比如Add,Insert,Remove,如果我们对ReadOnlyCollection的实例做强制类型转换在操作会有什么结果:
ReadOnlyCollection readonlyColl = new ReadOnlyCollection(userList);
((IList)readonlyColl).Add(new User());
如果运行这段代码会报错“Not supported exception”,这说明该集合的实现与一般的集合有所不同,CLR的实现者对这个做了特殊的处理。

最后说一个问题,如何对获得只读集合进行排序?我们无法排序,因为无法操作集合,如果要排序的话只能将集合的元素复制到自己的新集合中,在排序:
readonlyColl.CopyTo(new User[readonlyColl.Count],0);

2007年3月27日星期二

smart class or dumb data class

谈 到business object不得不考虑的一个问题就是对象的行为应该放在哪里的问题,也就是说我们的BO是应该只包含对象,还是也应该包含一些操作。Smart Class说的是第二种情况,这样的业务对象不仅包含数据同时也包含了一些方法逻辑,它们了解一些对自身的操作,比如读取数据,修改保存、判断合法性之 类。第一种情况中业务对像仅仅只包含数据,它们被称之为Dumb Data Object,再这种情况下我们通常都要另外定义一个对象来管理执行于这些数据对象之上的操作,称之为Data Object Manager。
Smart Class于我们通常使用的类一样,有数据也有操作,Rockford Lhotka的《C# 2005 Business Objects》 里面有详细的介绍,他比较推崇这种对象,并且为它们定义了非常丰富的操作(如果没有请留言我可以email给你电子版,大小为9m,所以最好有支持大附件的邮箱)。而Dumb Data Object的最大的好处是程序中的其它的逻辑层可以很好地与该类型的业务对象一起工作,比如Business layer和Data Access Layer可以引用并复用它们,这样我们定义的业务对象可以很容易地在系统的各部分传递,而不会出现任何问题。
Data Object Manager主要完成以下几个数据操作的功能:
  • GetItem
  • GetList
  • Insert
  • Update
  • Delete
这也是对应了基本的数据库操作,而业务对象的操作一般来说还包含一些业务处理方法,比如:
  • Search
  • Deactivate
  • Clone
  • Filter
其实现在有了像NHibernate这样的东西,业务对象(或manager)基本上已经不包含什么数据库的操作了,而是主要包含列表二中那些操作。如果是配合使用代码生成工具的人肯能更愿意使用Dumb Data Object。

2007年3月24日星期六

AutoUpdate:overview


AutoUpdate是一个基于webservice和ftp的更新工具,其中webservice用来发现和更新该工具中的更新引擎,而ftp用来传输更新文件。
左边的图显示了自动更新工具AutoUpdate的几个主要部件的协作关系。





  1. client启动,首先检查本地更新引擎的版本,如果引擎不存在则简单地返回“0.0.0.0”,也就是最低版本;如果引擎存在,则读取它的AssemblyVersion。
  2. client调用服务器端的updateservice,并将本地引擎版本传递给服务器
  3. server检查发布的最新引擎版本,如果新引擎版本高于client的版本,则返回需要更新引擎的消息给client
  4. client调用服务器的updateservice获得最新的引擎。引擎被序列化为二进制的数据,到本地由client反序列化并保存在客户机上。接着client启动更新引擎。
  5. 更新引擎调用服务器的web service获得更新描述文件updatemanifest.xml,然后解析该xml文件生成操作(opreation)序列。操作序列中的每个操作都包含了操作的文件对象和操作类型,比如是增加还是删除之类。
  6. 操作序列中的具体操作被顺序执行。如果有新增(覆盖也被归为新增)操作会先从服务器上下载文件。
我总结了一下,自动更新应该具有以下几个特点:
  • 更新设置应该在服务端设定,应为我们不可能去客户端更改设置
  • 更新的启动程序应该尽可能简单,最好只有一个功能:检查现有的更新引擎是否需要更新,如果需要则先下载该引擎
  • 真正的更新由更新引擎去执行,这样可以自由变更更新引擎
  • 更新内容有配置文件设定,这里下载xml文件来描述,具有通用性
  • 更新引擎一般比较小,可以通过webservice的方式直接传递到客户机
  • 更新文件通过ftp方式下载,可以保证快速更新
  • 支持从不同的低版本升级到最新版本。在服务端定义一个版本升级策略的映射文件,根据客户端传递过来的不同的版本号取不同的升级文件,比如oldversion_to_latestversion.xml

2007年3月16日星期五

MVP实践之:单元测试

MVP模式的应用中很重要一点是“方便了测试”。MVP中的Model层不与界面发生任何关系,按照一般的测试方法进行测试即可。但是Presenter需要view,至少需要一个View的契约,也就是它多少需要对view有一些了解,那么到底如何在没有界面的情况下测试呢?这里借用上一篇中讲到的winform的例子,以测试SearchPictOfComp为目标来实践一下。这里假设Model层的代码都经过了测试。

我们编写一个测试用例,如下:
[Test]
public void SearchArtifactOfCompTest()
{
MockViewListDescPict view;
view=测试用的View
view.ComponentId = "265f1e5d-2c75-4368-9a3a-5da5d82270c5";
ListDescPictPresenter presenter = new ListDescPictPresenter(view);
view.AttachPresenter(presenter);
view执行click button的动作
Assert.IsTrue(((IList)view.descPictList).Count == 6);
}
先解释一下这个测试用例的意思。首先创建一个测试用的view,然后用它初始化presenter,当view执行一个点击button的动作之后,presenter会去响应这个操作,将取出的数据传给view的列表控件显示,如果成功则列表控件的数据源中应该有6条纪录。
现在运行这个测试用例肯定是失败的,因为标记为红色的代码都是没有实现的伪代码。为了让测试通过,我们尝试实现红色标记处所要求的功能。
  • view=测试用的View
    这里实际上是要求一个满足契约IViewListDescPict的view,我们虽然没有真正的view,但是我们可以编写一个模拟的view(mock view)——可以联想在模块化测试时编写桩(stub)模块的情形。MockViewListDescPict是一个模拟的view,它实现了IViewListDescPict接口。那么一个问题就解决了:
    view=new MockViewListDescPict();
  • view执行click button的动作
    上一篇中讲到presenter是采用观察者模式,它监听button的事件。既然只要事件,那我们只要模拟一个事件就行了,并不需要真的用一个button。搞清楚button的click是产生一个普通的event,那我们定义一个:
    public EventHandler searchArtsOfComp_Event
    事件的触发很简单:searchArtsOfComp_Event.Invoke(null, null);
    我们把触发的代码放在一个方法中,以便在测试的时候模拟:
    public void MockClickSearchButton()
    {
    searchArtsOfComp_Event.Invoke(null, null);
    }
    接下来我们要让presenter注册对该事件的监听,以便在invoke触发事件的时候能被presenter捕捉到,我们把这一步放在AttachPresener中进行:
    public void AttachPresenter(ListDescPictPresenter presenter)
    {
    this.presenter = presenter;
    searchArtsOfComp_Event = presenter.SearchPictOfComp_EventHandler;
    }
    其中presenter.SearchPictOfComp_EventHandler是一个指向方法SearchPictOfComp(见应用篇)的委托。
    到此,第二个问题也解决了:
    view.MockClickSearchButton();
现在运行一下测试用例,pass了自然高兴,可是“(IList)view.descPictList”是怎么回事?其实这是为了测试读取的数据是不是正确地传给了view的列表控件,在解释测试用例的意思已经说过了。但是我们现在使用的view都是模拟(Mock)的,button事件也是假的,那么列表也同样可是假的了。我们并不关心列表控件是不是正确的显示了数据,那是控件自己的事情,我们只关心是不是传递了正确的数据,因此这里可以模拟一个接受数据的行为。控件的datasource是object的,那我们就在mock view的内部定义一个objcect的对象来获得对传递数据的引用:
public object descPictList;
public void SetPictListDataSource(IList pictList)
{
descPictList = pictList;
}
当我们模拟了button的click事件之后,presenter根据它的内部逻辑会去调用view的
SetPictListDataSource方法,将数据传递给我们设定的接收对象descPictList,现在我们只需要检查descPictList所获得的数据是否正确就行了。如果你愿意的话可以逐条检查descPictList中的项是否正确,但其实我们只需简单地检查项的个数就可以了,因为在presenter内部未对数据做修改,只要Model层能保证取出的数据无误,这里就不会有错。

总结一下对presenter的测试,一共有三点:
1、不需要真的view,可以使用类似Mock Object这样的技术模拟一个view,只要模拟的对象满足IView的契约即可。
2、如果要从view中获得用户的数据,比如要从textbox中取得用户输入的查询条件,又或者需要将处理的结果数据发回view显示,比如将一个list的集合发送到ListBox显示,这两类问题我们都可以使用对字段或者属性的存取操作来模拟。因为我们不关心显示,只要数据正确传送即可,将显示的任务交给view,可是使得presenter的功能独立于显示层。
3、对于像用户点击button或者选择chechbox这样的交互行为,我们不需要真的用控件来测试。.net允许我们手动触发事件,比如EventHandler.Invoke(),因此我们可以用手动触发事件的方式来模拟用户的交互行为

2007年3月15日星期四

MVP实践之:Winform应用

上面的界面显示了程序最终运行的状态,主要完成两个功能:
  1. 输入一个构件的ID号,根据Id号到ftp服务器上查找该构件所有相关的图片,并且将这些图片的名称显示在一个ListBox中。
  2. 改变选中的图片(列表中选图片名)时将图片显示在右边的PictureBox中。
上面说的那两个功能,基本上可以说是这个程序的需求了,根据要求先画出界面来。注意这里画界面仅仅只是为了明确所需要的功能,既然是MVP模式的实践,我们就必须摆脱双击控件然后直接编写代码的习惯。将MVP的三要素从这个程序剥离出来,分析清楚它们各自的角色和职责,以及如何进行交互才是最首要的工作。
三要素:
View:是一个Form,包含了一些控件
Model:这里的功能就是访问ftp取得图片信息
Presenter:从界面获得构件id,根据id从model取得图片名称列表,然后传给界面显示;从界面获得图片名,根据图片名从model取得图片,将图片传给界面显示。

右边的图是实现该程序的类图,有两点需要先说明一下:
  1. Model在哪里?
    前面的文章“MVP vs MVC”中讲到过,MVP中的Model并非DomainModel,其中甚至包含了业务逻辑,就如在本程序中充当Model角色的是一个Service类,它负责从数据源取得数据并向外提供,需要的时候会先对数据做一定的处理。这里的数据源是一个ftp服务器。
  2. IView***是干什么的?
    使用MVP的一个很重要的原因是为了方便测试,将界面与数据和控制逻辑分离之后,可以在不依赖于具体的界面的情况下对View以下的逻辑层(V、P层)的代码进行测试。但是如果没有了具体的View,presenter与View的通信如何完成呢?引入“契约”可以解决这个问题。契约的基本思想是:一个对象它保证为它的使用者(调用它的对象)提供它所承诺的功能,而承诺的功能就是契约中所规定的功能。比如有这样一个规定:
    string SelectedPictName { get;}
    意思是所有的使用者都能够得到一个PictName,而所有遵守这个契约的对象都保证提供一个PictName。
    那么,本例中的IViewListDescPict就是这样一个契约,它规定了所有实现了这一接口的View都必须保证提供接口中所规定的功能(类实现接口,就必须实现接口中所有的功能)。这样presenter就不需要一个具体的View了,它只需要一个view的契约,通过调用契约中的功能来帮助自己完成任务。
以根据id查找构件所有的图片为例,看一下是如何工作的:

步骤1-4都是一些初始化工作,窗体作为视图负责初始化自己,然后创建一个它需要使用的presenter并且将自己与presenter邦定(实际上就是获得一个对presenter的引用)。
private void FormListDescPict_Load(object sender, EventArgs e)
{
presenter =new ListDescPictPresenter(this);
AttachPresenter(presenter);//this.presenter=presenter;
}
步骤5中view接受一个用户响应,但是它自己并不做处理而是在步骤6中将处理委托给presenter。
//register the presenter as a observer
btnSearch.Click +=presenter.SearchPictOfComp_EventHandler;
步骤6-11是一套控制逻辑,全部在presenter内部处理。presenter监视view中的按钮(观察者模式),当检测到用户点击事件之后,先从view取得componentid,从service取得数据,然后刷新view的数据显示。
private void SearchPictOfComp(Object sender,EventArgs e)
{
try
{
IList descPicts = pictService.GetPictsOfComp(view.ComponentId);
view.SetPictListDataSource(descPicts);
}
catch (Exception ex)
{
//View will show the error msg to the end user
view.Message = ex.Message;
}
}
我们可以看到view也就是这里的窗体form仅仅只是将用户的输入向下层传递,它甚至不需要知道自己什么时候该刷新,只是定义了刷新的方法,真正的刷新命令由presenter发出。view中没有逻辑处理,这样可以使得view尽量的简化。复杂的控制逻辑都在presenter层处理,因此这些控制也就变得可以复用了,意味着我们可以将这一套逻辑处理用于多个view,理想的情况下可以同时支持winform的和asp.net的程序。

最后要提一下异常处理问题。由于view没有处理逻辑,使得我们在view中没有捕获异常的机会,因此只能在presenter层捕捉了。那么如何将捕获的异常消息通知view以显示给用户呢?本例中是在IView这个契约里定义了一个Message属性,presenter将捕获的异常消息写入这个属性中,只要实现IView接口的view都可以获取这个消息,并进行处理。
public string Message
{
get
{
return msg;
}
set
{
if (!string.IsNullOrEmpty(value))
{
msg = value;
//you can use the msg in the way you like
MessageBox.Show(msg);
}
}
}
上面的方法虽然是对于错误处理提出来的,但是同样可以用于其他的用途,比如我希望返回一个操作成功的提示消息。

2007年3月14日星期三

中消协与全国律协:房贷律师收费谁委托谁付费

人民网的消息:
本报北京3月13日讯 记者王比学今天从全国律师协会了解到:近来,广大消费者对房屋交易中抵押贷款环节上律师服务收费的支付多有质疑,为此中消协与全国律协申明,律师在为个人 购房抵押贷款提供法律服务过程中,要坚持谁委托谁付费的原则,即银行委托向银行收费,购房消费者委托由消费者付费,不违背消费者意愿转嫁服务收费。   为了维护广大购房抵押贷款人的合法权益,构建公平正义、诚信守规、安定有序的和谐消费环境,推动行业自律,发挥律师在房地产市场中的积极作用, 敦促相关的经营者在个人购房抵押贷款中规范自身的经营行为,中消协和全国律协今天还联合倡议:律师在代理个人购房抵押贷款业务过程中,要恪守律师职业道德 和执业纪律规范,勤勉尽责,维护当事人的合法权益;消费者在申请购房抵押贷款时,有权自主委托律师,也有权拒绝向非自主委托的律师付费。

2007年3月12日星期一

A MVP Framework for .net

Today, I find a project about MVP pattern on CodePlex.com. That project is named NMVP which means a Model-View-Presenter Framework for .Net. Here is the description from the NMVP Home:
"NMVP is an acronym for .Net Model-View-Presenter Framework.
The NMVP Framework is a framework for helping in building Model-View-Presenter (MVP) based UI architectures.

Even though it´s possible to create very simple MVP Architectures using NMVP, it really shines when used to build Composite MVP Architectures. If you don´t know MVP yet, please check out our Links page. There are several good articles about it there."
I think it's a very interesting and useful framework.Click here to know more about it .

2007年3月8日星期四

Blogger is ready for me

好久没上Google的BLogger了,进去知道才知道原来google已经将blog升级为正式版了。

Check out the new Blogger

We're out of beta and ready to go.

Google没将“beta”标记去掉,而是选择在它上面划上一个“叉”。

Cool,Google总是与众不同!

新版的Blogger已经可以与Gmail帐户绑定了,到现在为止Google的服务基本上都可以使用统一的帐户来访问了。我本来就有Gmail帐户的,只需要激活一下就可以使用Blogger了,非常的方便:
Now, I'm here!

使用之后感觉比Msn space强太多了,总体来说:功能强大,使用方便,访问速度也快,帮住中心更是提供了中文的帮助文档。
对于我所耿耿于怀的问题Blogger都给出了很好的解决方式:
1、在日志中插入图片。msn知道怎么想的,还要自己编辑html,提供了Windows Live Writter却不能上传图片,抓狂!而Blogger可以直接插入图片,也可以选择用Picassa上传。
2、Label标签或说Tag,space的简直太鸡肋了,就一个分类。
3、无法直接访问某一篇blog文章,因为它没有为blog文章生成页面,只能通过什么****entity!……,我估计很多都还不知道可以这样呢!
4、添加扩展功能非常麻烦,不像Blogger有一个简单的GUI,比如要把我的del.cio.us融入blogger只需一步就搞定了,简洁明了
5、存档管理清楚地显示了按年、月、周存档的文章信息,而space呢就一个某年某月,如果某个月没有文章俺们还得傻傻地点开来才知道呢。

还有好多呢,不说了,你自己去体会吧

MVP vs MVC

这两天一直在看MVP相关的东西,什么是MVP,怎么实现MVP可以参考下面两篇文章:
http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/default.aspx
(中文版:http://www.microsoft.com/china/msdn/library/architecture/architecture/architecturetopic/MVP.mspx?mfr=true)
http://www.codeproject.com/useritems/ModelViewPresenter.asp
MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。作为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通信是通过Presenter (MVC中的Controller)来进行的,所有的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是通过 Controller。Alex在他的blog中对于这两者之间的比较很直观也比较清楚,原文可以下面的地址找到:
http://ameleta.spaces.live.com/blog/cns!5F6316345A821420!163.entry


【译文】:

Model View Presenter vs Model View Controller

简介

在我工作中经常需要处理一些由于开发人员没能很清楚地理解MVC和MVP模式的区别的情况下使用它们而产生的问题。在这篇文章中我将会阐述一下我对两者之间区别的一些理解。
在N层体系结构中MVC/P模式仅仅只是用于表示层(presentation layer),理解这一点很重要。这两个模式并不是关于怎么构建数据层(data layer)和服务层(service layer)的,而是关于怎么将数据(data)从用户接口(view)中分离出来,以及用户接口如何与数据进行交互的。这些模式的使用让解除你的程序中表示层对对数据和控制逻辑的依赖,从而可以自由的变更表示层。

这两种模式中三个部分的一般理解

1、模型(Model)表示数据模型和业务逻辑(business logic)。模型并不总是DataSet,DataTable之类的东西,它代表着一类组件(components)或类(class),这些组件或类可以向外部提供数据,同时也能从外部获取数据并将这些数据存储在某个地方。简单的理解,可以把模型想象成“外观类(facade class)”。【译注:这里的外观是指“外观模式”中所说的外观。外观的一般作用是为一个复杂的子系统提供高层次的简单易用的访问接口,可以参看下面的图来理解它的原理:


2、视图(View)将数据层现给用户。一般的视图都只是包含用户界面(UI),而不包含界面逻辑。比如,Asp.net中包含控件的页面(page)就是一个视图。视图可以从模型中读取数据,但是不能修改或更新模型。
3、层现器(Presenter)/控制器(Controller)包含了根据用户在视图中的行为去更新模型的逻辑。视图仅仅只是将用户的行为告知控制器,而控制器负责从视图中取得数据然后发送给模型。

MVC/P模式的核心是为了将模型从视图/控制器中分离出来,从而使得模型独立于它们,因此模型不包含对视图和控制的引用。

什么是MVC(Model View Presenter)模式?

1、为了使得视图接口可以与模型和控制器进行交互,控制器执行一些初始化事件
2、用户通过视图(用户接口)执行一些操作
3、控制器处理用户行为(可以用观察着模式实现)并通知模型进行更新
4、模型引发一些事件,以便将改变发告知视图
5、视图处理模型变更的事件,然后显示新的模型数据
6、用户接口等待用户的进一步操作

这一模式的有一下几个要点:
1、视图并不使用控制器去更新模型。控制器负责处理从视图发送过来的用户操作并通过与模型的交互进行数据的更新
2、控制器可以和视图融合在一块。Visual Studion中对Windows Forms的默认处理方式就是这样的。【译注:比如我们双击一个Button,然后在它的事件里写处理逻辑,然后将处理的数据写回模型中。这里处理逻辑时间应该是控制器的功能,但是我们并没有专门写一个控制器来做这件事情而是接受了VS的默认处理方式,将它写在Form的代码中,而这里的Form在MVC中它就是一个View。所以这说vs默认的处理方式是将把控制器和视图融合在一起的。】
3、控制器不包含对视图的渲染逻辑(rendering logic)

“主动—MVC”模式,也是通常意义下的MVC模式

【译注:为什么说是主动的?View不是等Controller通知它Model更新了然后才从Model取数据并更新显示,而是自己监视Model的更新(如果用观察者模式)或主动询问Model是否更新。前面那种等待Controller通知的方式是下面所介绍的“被动—MVC”的实现方式。】

“被动—MVC”模式
与主动MVC的区别在于:
1、模型对视图和控制器一无所知,它仅仅是被它们使用
2、控制器使用视图,并通知它更新数据显示
3、视图仅仅是在控制器通知它去模型取数据的时候它才这么做(视图并不会订阅或监视模型的更新)
4、控制器负责处理模型数据的变化
5、控制器可以包含对视图的渲染逻辑

MVP模式

与“被动—MVC模式”很接近,区别在于“视图并不使用模型”。在MVP模式中视图和模型是完全分离的,他们通过Presenter进行交互。
Presenter与控制器非常相似,但是它们也有一些的区别:
1、Presenter处理视图发送过来的用户操作(在MVC中视图自己处理了这些操作)
2、它用更新过的数据去更新模型(在被动MVC中控制器只是通知视图去更新过的模型中去取新的数据,而主动MVC中模型通知视图去更新显示,控制器不需要做工作)
3、检查模型的更新(与被动MVC一样)
4、(与MVC的主要区别)从模型中取数据然后将它们发送到视图中
5、(与MVC的主要区别)将所做的更新告知视图
6、(与MVC的区别)用Presenter渲染视图

MVP的优势

1、模型与视图完全分离,我们可以修改视图而不影响模型
2、可以更高效地使用模型,因为所以的交互都发生在一个地方——Presenter内部
3、我们可以将一个Presener用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁。
4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

MVP的问题

由于对视图的渲染放在了Presenter中,所以视图和Persenter的交互会过于频繁。

还有一点你需要明白,如果Presenter过多地渲染了视图,往往会使得它与特定的视图的联系过于紧密。一旦视图需要变更,那么Presenter也需要变更了。比如说,原本用来呈现Html的Presenter现在也需要用于呈现Pdf了,那么视图很有可能也需要变更。