65.9K
CodeProject 正在变化。 阅读更多。
Home

分离域与表示 - 第二部分

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.89/5 (6投票s)

2009 年 7 月 17 日

CDDL

5分钟阅读

viewsIcon

17024

关于“分离域与表示”系列文章的第二篇。

来自 IRefactor 的转载

这是本系列文章“分离领域与表示”重构的第二篇文章。

之前的文章

上次,我们讨论了在IRefactor.CoursesView项目中将表示层(FrmMain)与领域对象(CoursesDS)断开连接的方法。结果是,我们不再拥有臃肿的、包含一切的初始项目,而是得到了以下结构:

  • IRefactor.CoursesView – 代表不包含领域对象的ViewCoursesView)。
  • IRefactor.Common – 代表不包含UI元素的领域对象(CoursesDS)。

现在是时候进一步分离UI和BL了。为此,我将使用MVP模式。似乎关于UI/BL分离模式的定义存在很多误解(可以看这里),我将专注于以下定义:

在我的文章中,MVP = Model-View-Presenter 将基本代表:

  • Model – 嘿,我是领域模型;

    我知道如何操作模型对象以执行所需的应用程序逻辑和持久化。

    我不知道如何向用户可视化任何信息,也不知道如何响应用户可能采取的任何操作。

  • View – 嘿,我是视图模型;

    我知道如何向用户可视化信息(这些信息将由Presenter提供给我)。

    我知道如何执行简单的数据绑定以及可能修改屏幕视觉布局的简单UI操作。

    我不知道在需要应用程序逻辑或持久化时该做什么。

  • Presenter – 嘿,我是Presenter模型;

    我知道如何处理用户对View的请求(这些请求比简单的数据绑定更复杂),以及如何将这些请求委托给Model。

    我知道如何查询Model,以便在需要显示任何信息给用户时,将信息委托给View。

    我不知道如何以图形方式绘制控件(这是View的职责),也不知道如何执行任何应用程序逻辑来推导出该信息(这是Model的职责)。

那些眼睛敏锐的人可能会注意到这里使用了Supervising Controller(这不会立即在重构的精神下对代码进行剧烈更改。稍后,可以将View转换为Passive View,同时继续重构)。

MVP - Supervising Controller

重构步骤

  • IRefactor.CoursesView.FrmMain类重命名为CoursesView。进入FrmMain,右键单击它,然后使用Refactor » Rename命令轻松重命名它。
  • IRefactor.CoursesView中创建一个名为CoursesPresenter的类。

    SeparateDomainFromPresentation.CoursesPresenter

  • CoursesPresenter类中添加一个指向CoursesView的指针(请注意,视图是只读的)。
    public class CoursesPresenter
    {
     private readonly CoursesView view;
    }
  • CoursesPresenter中添加一个接收CoursesView实例的构造函数。
    public CoursesPresenter(CoursesView view)
    {
     this.view = view;
    }
  • 编译解决方案并执行单元测试。
  • 现在我们需要将用户交互从视图委托给Presenter。我们可以通过继承EventArgs并创建CoursesEventArgs类来实现,或者我们可以让CoursesPresenter直接查询CoursesView并获取所需数据。这里,我将直接获取CoursesDS领域对象。向CoursesView添加以下内容:
    public CoursesDS Courses
    {
     get { return coursesDS; }
    }
  • 让我们从Save事件的委托开始。如果您仔细查看coursesBindingNavigatorSaveItem_Click事件处理程序,您会发现该方法具有两个不同的职责。它处理所需的数据绑定,然后执行数据访问操作以保存CoursesDS领域对象。为了分离关注点,让我们使用另一个重构步骤,称为“提取方法”。选择数据访问代码,右键单击它,然后使用Refactor » Extract Method命令将代码提取到一个名为“Save”的新方法中。

    SeparateDomainFromPresentation.SaveItem

    // ...
    this.Validate();
    this.bsCourses.EndEdit();
    // Changed from auto generated code.
    Save();
    // ...
    private void Save()
    {
       if (coursesDS.HasChanges())
       {
           CoursesDS.CoursesDataTable changes =
               this.coursesDS.Courses.GetChanges() as
                   CoursesDS.CoursesDataTable;
           if (changes != null)
           {
               taCourses.Update(changes);
           }
       }
    }
  • 编译解决方案并执行单元测试。
  • 在分解了coursesBindingNavigatorSaveItem_Click方法后,我们突然意识到Save方法不属于CoursesView类,因为它执行数据访问操作。无论如何,此操作应该在领域模型(业务逻辑)内部。在此期间,我们将该方法推送到Presenter中。
  • CoursesPresenter中,创建一个名为Save的新方法。该方法将从CoursesView检索CoursesDS领域对象,并将该对象保存到数据库中。
    public void Save()
    {
       CoursesDS coursesDS = view.Courses;
       //...
    }
  • 编译解决方案并执行单元测试。
  • CoursesView.Save方法中的所有代码复制到CoursesPresenter.Save方法中,并调整新代码以适应其新的“位置”(请注意CoursesTableAdapter需要重新定义)。
    public void Save()
    {
        CoursesDS coursesDS = view.Courses;
        if (coursesDS.HasChanges())
        {
            CoursesDS.CoursesDataTable changes =
                coursesDS.Courses.GetChanges()
                    as CoursesDS.CoursesDataTable;
            if (changes != null)
            {
               using (CoursesTableAdapter taCourses = new CoursesTableAdapter())
               {
                   taCourses.Update(changes);
               }
            }
        }
    }
  • 编译解决方案。
  • 现在,有趣的部分来了;将CoursesView.Save方法中的所有代码放在注释中,并声明一个调用其Save方法的CoursesPresenter对象。
    private void Save()
    {
        //if (coursesDS.HasChanges())
        //{
        //    CoursesDS.CoursesDataTable changes =
        //        this.coursesDS.Courses.GetChanges()
        //            as CoursesDS.CoursesDataTable;
        //    if (changes != null)
        //    {
        //        taCourses.Update(changes);
        //    }
        //}
        CoursesPresenter presenter = new CoursesPresenter(this);
        presenter.Save();
    }
  • 编译解决方案并执行单元测试。
  • 瞧!您已成功将数据访问方法从视图移至Presenter。通过持续的重构,您可以将该方法进一步推送到数据访问层。

快速总结

  • 我们引入了一个新的Presenter类,名为CoursesPresenter
  • 我们将Save方法(执行数据访问操作)从视图移到了Presenter类中。(别担心,我们将在下一篇文章中从CoursesView中删除Save方法。)
  • 同样的操作也应该应用于Load方法(FrmMain_Load)。这里不展示,只需遵循相同的原理。

这是当前IRefactor.CoursesView项目的示意图。

SeparateDomainFromPresentation.MVP

显然,这与我们之前描绘的MVP模式不同。未来的文章将解释重构为MVP模式的额外步骤,通过应用

  • 事件 – Presenter通过订阅View来处理复杂的用户事件
  • 接口 – Presenter只能通过接口操作View。否则将破坏View的封装。

参考文献

请参阅此处,了解关于智能客户端开发的精彩总结。

© . All rights reserved.