作者:Billy McCafferty 翻译:张善友

原文地址:http://www.codeproject.com/useritems/ModelViewPresenter.asp

 

这篇文章描述了ASP.NET 2.0使用Model-View-Presenter 模式实现业务逻辑与表现层的适当分离。

概述

   经过多年代的ASP代码积累,微软开发了具有一流水平的网络平台:ASP.NET. ASP.NET使用后置代码页面方式隔离业务逻辑。虽然用心良苦,但是ASP.NET在企业级应用开发方面还是存在如下的不足:

l        后 置代码页中混合了表现层,业务逻辑层,数据访问层的代码。之所以出现这种情况是因为后置代码充当了事件引发,流程控制,业务规则和表现逻辑,业务逻辑和数 据访问的协调者等多种角色。后置代码页充当这么多的职责导致许多难处理的代码。在企业应用中,一个良好的设计原则是各层之间的适当分离和保持后置代码页内 容的尽可能干净。使用Model-View-Presenter 模式,后置代码的内容将非常简单,严格的管理表现层内容。

l        后置代码模型的另一个缺点是它难以不借助帮助类/工具类实现重用后置代码页面之间的可重用代码。很明显的,这也是提供了一个适当的解决方案,但往往导致ASP式的类,不像是一流的对象。通过适当的设计,每个类都应有清晰的职责,通常一个叫

ContainsDuplicatePresentationCodeBetweenThisAndThat.cs并不合适

l        最后,对后置代码页进行单元测试非常困难因为它们同表现层的太紧密了,当然可以选择NUnitASP这样的工具,但是他们非常的耗费时间,并且难以维护。单元测试应当是简单快速的。

 

 可以采用各种技术手段是后置代码页保持分离。例如Castle MonoRail项目仿效Ruby-On-Rails , 但是放弃了ASP.NET的事件模型。Maverick.NET是一个支持ASP.NET事件模型的框架但是保留后置代码页作为程序的控制器。理想的解决 方案是使用ASP.NET的事件模型并保持后置代码页的尽可能简单。Model-View-Presenter 模式是一个不需要借助第三方框架实现这个目标。

 

Model-View-Presenter

Model-View-Presenter (MVP) 模式是 Model-View-Controller (MVC) 模式的变种,针对事件模型,像ASP.NET这样的框架。具体参看简单介绍GUI设计模式(MVPMVP最初使用与Dolphin Smalltalk. 主要的变化是Presenter实现MVC的Observer设计,基本设计和MVC相同:Model存储数据,View表示Model的表 现,Presenter协调两者之间的通信。在 MVP 中 view 接收到事件,然后会将它们传递到 Presenter, 如何具体处理这些事件,将由 Presenter 来完成。关于在 MVC 和 MVP的深入比较,请查看

http://www.darronschall.com/weblog/archives/000113.cfm.,接下来以三个例子详细说明MVP模式。

 

最简单的例子

  这个例子,客户想在页面上显示当前的时间(从简单的开始容易理解)。显示时间的ASPX页面是“View”。Presenter负责决定现在的时间(Model,而且把Model告知 View。我们从一个单元测试开始。

 

[TestFixture]

public class CurrentTimePresenterTests {

    [Test]

    public void TestInitView() {

        MockCurrentTimeView view = new MockCurrentTimeView();

        CurrentTimePresenter presenter = new CurrentTimePresenter(view);

        presenter.InitView();

 

        Assert.IsTrue(view.CurrentTime > DateTime.MinValue);

    }

 

    private class MockCurrentTimeView : ICurrentTimeView {

        public DateTime CurrentTime {

            set { currentTime = value; }

 

            // This getter won't be required by ICurrentTimeView,

            // but it allows us to unit test its value.

            get { return currentTime; }

        }

 

        private DateTime currentTime = DateTime.MinValue;

    }

}

上 面的单元测试代码和右边的类图,描述了MVP各个元素之间的关系。单元测试中创建的第一个对象实例是MockCurrentTimeView,从这个单元 测试中可以看出,所有的表现逻辑的单元测试并没有一个ASPX页面(View),所需要的是一个实现视图接口的对象;因此可以创建一个视图的模拟对象 (Mockview)代替真正的视图对象。

下一行代码创建了一个Presenter的对象实例,通过它的构造函数传递了一个实现ICurrentTimeView接口的对象,这样,Presenter现在能够操作View,从类图中可以看出,Presenter只与View的接口通信。这允许实现相同的View接口的多个View被Presenter使用。

最后,Presenter调用InitView()方法,这个方法将获取当前的时间并通过公开的属性ICurrentTimeView传递给视图(View),单元测试断言CurrentTime的值应比它的初始值大(如果需要可以做更多的断言)。

那么现在要做的就是要运行单位测试并通过了!

 

ICurrentTimeView.cs 视图接口

使单元测试编译通过的第一步是创建ICurrentTimeView.cs,这个接口提供Presenter 和 View之间的沟通桥梁,在这个例子中,视图接口需要暴露一个Model数据,使Persenter能够将Model(当前时间)传递给View。

public interface ICurrentTimeView {

    DateTime CurrentTime { set; }

}

因为只需要显示模型数据,视图接口中只需要一个CuttentTime的Set;但是设置了一个Get,用于在单元测试中获取视图的CurrentTime,它也可以添加到MockCurrentTimeView而不要在接口中定义,这样,在视图接口中暴露的接口属性不需要定义getter/setter(上面的单元测试就使用了这个技术)。

 

CurrentTimePresenter.cs - The Presenter

Presenter处理同Model之间的逻辑并将Model传递给View。要使单元测试通过编译,Presenter的实现代码如下:

public class CurrentTimePresenter {

    public CurrentTimePresenter(ICurrentTimeView view) {

        if (view == null) throw new ArgumentNullException("view may not be null");

 

        this.view = view;

    }

 

    public void InitView() {

        view.CurrentTime = DateTime.Now;

    }

 

    private ICurrentTimeView view;

}

完成上述代码,我们就完成了Unit Test,mock view,Presenter和View.单元测试现在可以成功编译并通过。下一个步骤是创建ASPX页面充当真正的View。

注意到ArgumentNullException异常的检查,这项技术被称为基于契约设计(Design By Contract),在代码中象这样做必要的检查可以大大的降低Bug的数量。关于基于契约设计(Design By Contract)的更多信息请参考http://archive.eiffel.com/doc/manuals/technology/contracthttp://www.codeproject.com/csharp/designbycontract.asp.

 

ShowMeTheTime.aspx - The View

这个页面需要做以下内容:

l        ASPX页面需要提供一个方法显示当前的时间,用一个Label控件显示时间

l        后置代码必须实现接口IcurrentTimeView

l        后置代码必须创建一个Presenter对象,并把自己传递给它的构造函数

l        创建好Persenter对象后,需要调用InitView()

 

ASPX 页面:

 

<asp:Label >另一个用户控件引发的Post-back对这个用户控件的影响。即使你没有使用MVP,也是一个好问题。


相关文章: