2007年8月31日星期五

第五章 在模块中添加MVP模式兼容的视图

本文会涉及到如下内容:

  • 什么是MVP
  • 使用SCSF创建一个视图(View)
  • 设计视图(View)
  • 实现视图(View)逻辑
  • 在Shell中显示View
  • 实现Presenter逻辑
  • 如何实现ISmartPartInfoProvider

一、什么是MVP

当我们在写界面程序时,常常包含各种各样的界面控件,用户事件,事件响应的控制逻辑,如果将这些代码全部放在一个界面类中的话,该类会非常复杂,并且很难进行单元测试,同时,也很难在不同界面中共享相同的行为或服务。

MVP的提出就是为了解决上述问题,它将数据显示和事件处理分离为两个不同的类 - View和Presenter,View负责将数据显示在界面上,并且将事件处理转给Presenter类。下图显示了该模式中各个类之间的关系:

这里的Model拥有View显示的数据,数据状态的改变是有Presenter控制的。

详细定义参考Martin Folwer 的 UI Architecutre

二、使用SCSF创建一个MVP模式兼容的视图

打开一个业务模块项目,在Views目录下,点击鼠标右键,选择“Smart client Factory” ->"Add View (with Presenter)",创建View向导会被打开,输入View的名字(TestView),选中“Create a folder for the view”,这样,系统会为该View创建一个单独的目录,点击完成,向导会创建以下相关的类:

  • ITestView - 该文件是一个为View设计的空的接口,Presenter使用该接口而不是具体的View实现。
  • TestView - 该类继承UserControl基类,可以在该类中设计用户界面,同时该类实现了ITestView接口并包含了一个TestViewPresenter实例,当有用户交互操作的时候,可以使用该Presenter调用相关的业务逻辑和接口。
  • TestViewPresenter - 该类继承了基础的Infrastructure.Interface.Presenter类,包含了View所需要的业务逻辑

设计视图(View)

打开TestView设计,在该界面中添加所需的控件。详细的介绍参考界面设计相关主题。

三、实现视图(View)的业务逻辑

View的设计完成后,我们就要考虑如何将数据显示在用户界面上,一般来说,在处理View的业务逻辑需要考虑两件事情,一件是如何将数据绑定到View的控件上,另外一件是如何处理事件。

数据显示:

由于View只是负责数据的显示(比如将一个Business Entity实例的变量显示),它并不知道数据是如何获取的或更新的,而数据的获取或更新是由Presenter负责,因此,为了能让Presenter将更新的数据及时通知View显示,并且Presenter不需要知道View的具体实现,因此在IView(比如,ITestView)接口中定义一些通用的接口(比如:BindDataToView(Person person))。在View的具体实现类中,我们重载该方法,将数据显示在View中。一旦Presenter知道数据发生更新,可以在Presenter中调用该接口,这样就可以通知所有对该数据更新关心的具体的View。

事件处理:

在View中需要处理的另外一件事情是如何处理View中的事件,比如Button响应事件等。对于每一个事件处理,我们可以在Presenter中定义一个方法,用来处理不同的事件,而在View中,我们需要为每个Button事件创建一个Stub方法,在该方法中调用相应的Presenter方法。对于事件的处理,我们也可以用Command或Event Subscriber&Publish模式,详细参考各自文章。

接下来的一个步骤是将视图(View)显示在Shell中

四、显示View

下列的代码是用来将View显示在Shell中的

TestView view = WorkItem.SmartParts.AddNew<TestView>("TestView");

WorkItem.Workspaces[WorkspaceNames.XXXXXX].show(view);

五、实现Presenter逻辑

前面我们提到显示在View的数据更新,事件的处理是通过Presenter来实现的,因此,在Presenter中,我们可能会调用各种各样的服务(service)来实现真正的业务逻辑,比如调用WebService Proxy。下面的任务将要告诉我们如何实现Presenter调用相关的服务(service)。

  • 如何将Service加到WorkItem中

在业务模块中,打开ModuleController类,找到AddServices方法,使用下述代码将一个Service添加到WorkItem的Service集合(Collection)中。

WorkItem.Services.AddNew(MyService)();

这样,我们就将一个Service实例添加到WorkItem的Services集合中,以便在该模块中的其他类使用。

  • 如何在View载入的时候,调用相关的Service

在View显示或载入的时候,往往会显示一些初始的数据,这是在Presenter的OnViewReady方法中实现的。同时在Presenter中,我们会定义一些服务(Service)变量,在创建Presenter实例的时候,我们需要将这些对象赋值。在这里我们利用了Injection方式来初始化这些服务。

[InjectionConstructor]

public TestViewPresenter

(

[ServiceDependency] MyService1 myservice1,

[ServiceDependency] MyService2 myservice2

)

{

_myservice1 = myservice1;

_myservice2 = myservice2;

}

在上面的代码里面,属性(Attribute)[InjectionConstructor]是应用在构造函数用来告诉ObjectBuilder当创建一个Presenter实例的时候调用该构造函数。而[ServiceDependency]属性是用来告诉ObjectBuilder去WorkItem中检索一个存在的Service实例,并将该实例作为值赋予一个变量。ObjectBuilder首先会在当前的WorkItem中寻找对应的Service实例,如果找不到,则会到上一层的WorkItem中寻找,直到Root WorkItem,当在RootWorkItem中找不到实例的话,会抛出异常。这样,我们就可以在Presenter中使用定义好的服务。

六、如何实现ISmartPartInfoProvider

当我们定义SmartPart的时候,那些使用该SmartPart的组件希望能获取关于SmartPart更多更丰富的信息,该功能是由ISmartPartInfo来实现的,在CAB中,我们定义了一个名为ISmartPartInfoProvider Interface,该接口只定义了一个方法:

public interface ISmartPartInfoProvider

{

ISmartPartInfo GetSmartPartInfo(Type smartPartInfoType);

}

该方法返回一个ISmartPartInfo,和SmartPart相关的丰富的信息可定义在里面,因此我们可以在定义View的时候实现该接口,用来提供更多的信息。

打开TestView,修改如下:

public partial class TestView : UserControl, ITestView, ISmartPartInfoProvider

实现方法如下:

public ISmartPartInfo GetSmartPartInfo(Type smartPartInfo)

{

return _presenter.GetSmartPartInfo(smartPartInfo);

}

当一个外部组件(Shell 中的Workspace)请求获取View的SmartPartInfo的时候,我们都将该请求转发给Presenter来完成。我们可以将当前View显示的数据信息通过Presenter发送给Workspace。

此致,创建一个MVP模式兼容的视图,以及相关的设计已经完成,下一章节中我们将讨论如何调用WebService。

没有评论: