zoukankan      html  css  js  c++  java
  • 从Event折腾到Command

    (一)传统编程模型

      在传统的窗体编程模型中,包括.NET、Winform、WPF和Silverlight,Visual Studio会为我们分别提供不同的项目模板,如下所示:

    从Event折腾到Command

      于是,我们得以创建项目如下(以Winform为例):

    从Event折腾到Command

      注意到,我们看到的Form1可视化界面是由Visual Studio为我们自动生成的,它实际上是由Form1.cs和Form1.Designer.cs两部分组成的,它们是同一个类Form1的两个部分。我们从来不会去修改Form1.Designer.cs,因为它是由Visual Studio来自动维护的。当我们触发一些事件时,比如说,双击窗体从而默认添加Form1的Load事件,这时,Visual Studio就会做两件事情:

      1) 在Form1.cs中添加Form1_Load方法:       

        private void Form1_Load(object sender, EventArgs e)
        {
        
        }

      2) 在Form1.Designer.cs中把Form1_Load方法添加到Form1的Load事件上,从而在Form加载的时候,会触发这个方法:  

    this.Load += new System.EventHandler(this.Form1_Load);

      于是,我们所要做的工作简化为——只需在Form1_Load方法添加自己的逻辑就可以了,生产率得到大幅提升。但恰恰是这一优点,使得很多程序员只会拖拖控件,写写方法,而不晓得这其中的因果联系。这也同时验证了微软是在为懒人设计开发工具的理念。

      我们举的例子是Winform的,但是逐一分析ASP.NET、WPF和Silverlight,你会发现,都是一样的。

      在ASP.NET中,分为服务器和客户端两部分代码。说得俗一点,ASP.NET所要做的工作就是根据服务器端的.NET代码生成客户端的HTML代码。而服务器端的.NET代码,也采取了上述这种Event编程模式,如下所示:

      比如说,在一个apsx页面添加一个Button:                  

    <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" >
        <head runat="server">
          <title></title>
        </head>
    <body>
      <form id="form1" runat="server">
        <div>
          <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
        </div>
      </form>
    </body>
    </html>

    this.Load += Page_Load;

      但可惜的是,就是找不到~~。

      也许你会问WPF和Silverlight中的XAML是虾米,我认为哦,XAML的概念借鉴了HTML的思想,但是又有极大的不同。

      为什么这么说呢?我们知道,二者都是标签语言,前者通过IE等浏览器而后者借助Blender等设计器,都可以达到所见即所得的效果,而区别又在哪里呢?

      ASP.NET模型中,WebForm本身具有一棵控件树,在运行期它会把这棵树转换为一个HTML文件并显示;而WPF/Silverlight本身也有一棵控件树,通过序列化XAML来填充这棵树,最终会显示出这棵树。

      二者区别就在于,HTML在ASP.NET中是显示的结果,而XAML在WPF/Silverlight中是源头。

      我们在Visual Studio中建立一个WPF项目,会看到一个窗体对应Window1.xaml和Window1.xaml.cs两个文件。前者就是XAML了,而后者则用来放置那些Button1_Click、checkBox1_Checked方法的。

    从Event折腾到Command

      于是又有人要问了,控件的声明以及事件的绑定在哪里?不同于Winform的Form1.Designer.cs,在WPF中,放置这些代码的文件是一个名为Window1.g.i.cs的隐藏文件,它是readonly的,就是说,只允许Visual Studio自动修改,有兴趣的朋友可以在Window1.cs文件中Window1的构造函数里点击InitializeComponent方法,就会跳转到该隐藏文件(代码就不贴了)。      

        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                label1.Content = "Hello World";
            }
            private void checkBox1_Checked(object sender, RoutedEventArgs e)
            {
                label1.Content = "Open Seasam";
            }
        }

    当Window1中控件之间的交互越来越多时,这种好处就越明显。我们将Window1称为中介者(Mediator)——就是说,窗体编程模型普遍采用了中介者模式,从ASP.NET、Winform、到WPF、Silverlight,无一不是如此。

      关于中介者模式的介绍,请参见我的另一篇文章:《我也设计模式——Mediator》

      (二)传统编程模式的缺点以及一些解决方案

      关于Command模式的基本概念,请参见我的另一篇文章:《我也设计模式——Command》。

      话说,就在Event编程模式大行其道的时候,一些显著的问题也摆在了我们面前:

      1)虽然控件之间的交互完全都扔给Form窗体这个中介者了,但是我们发现,随着逻辑的越来越复杂,Form窗体中的代码也越来越多,动辄几千甚至上万。

      2)随着单元测试的普及,越来越多的程序员要求在他们的窗体程序中对UI相关的逻辑进行测试,但是原有的Event编程模式把UI和逻辑混在了一起,这使得单元测试无法进行。

      为此,我们通常会额外编写一个代理类,起个带有Helper后缀的名字。

      比如上面那个点击Button改变Label内容的button1_Click方法,可以重构为:      

        public partial class Window1 : Window
        {
            private XXXHelper helper = new XXXHelper();
            public Window1()
            {
                InitializeComponent();
            }
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                string text = helper.GetContent();
                label1.Content = text;
            }
        }
        public class XXXHelper
        {
            public string GetContent()
            {
                return "Hello World";
            }
        }

    所以说,MVP模式是最适合于在WPF/Silverlight中运用的了。

      对于问题二,也就是如何在UI中做UnitTest,是近几年来程序界广泛讨论的一个话题。

      我们究竟是要测试控件,还是要测试控件背后的数据?

      这个问题进一步归结为:我们究竟是面向控件编程,还是面向数据编程?

      貌似从编程伊始我们就面向控件编程,就是说,每次获得数据,就想法设法把这些数据分派到各个控件的各个属性上,而从来没有真正关心过数据。

      但越来越多的实战经验告诉我们,应该把更多的精力放到数据本身上,而不是其在控件上的表现形式。

      于是,我们引进了MVP模式,将UI一拆为二:界面(View)和数据(Model);二者之间通过Presenter来互通有无,就是说在View中触发事件引起Model的改变都封装成方法放在Presenter。

      这样,我在HP的项目中,建立了这样的测试模型,单独对View做验收测试(AcceptTest),也就是自动化测试,利用UIA或者White框架;而单元测试则建立在Presenter的那些方法之上,用以测试Model中的数据。详细信息请关注这个系列的下两篇文章:《Prism中的 UnitTest》和《Prism中的AcceptTest》。

      (三)Command应运而生

      Command是和MVP模式相辅相成,因此,在阅读本章之前,建议读者参考这个系列的另一篇文章:《MVP模式的前世今生》。

      就在传统Event编程模式满足不了我们复杂的业务逻辑时,MVP模式出现了,尤其是在WPF和Silverlight项目中,强大的数据绑定技术使MVP模式得以完美诠释。

      但是,一个严峻的问题摆在了眼前:为了使View最简单,就像下面的代码一样(这是一个MVVM的例子):

        public partial class ListItemContentView : UserControl
        {
            public ListItemContentView()
            {
                InitializeComponent();
            }
            public ListItemContentView(ListItemContentViewModel viewModel)
                : this()
            {
                this.ViewModel = viewModel;
                this.ViewModel.View = this;
            }
            public ListItemContentViewModel ViewModel
            {
                get
                {
                    return this.DataContext as ListItemContentViewModel;
                }
                set
                {
                    this.DataContext = value;
                }
            }
        }

      可以看到,View中没有任何事件绑定的方法,例如Button的Click事件,GridView的LoadRow事件(用于Silverlight中的数据逐行加载到GridView中)。那么这些事件都何去何从了呢?

      大致分为三种情况:

      1)一部分要使用AttachBehavior来自定义实现Command,比如说Button的Click事件,这是基于AttachBehavior 来实现的,Prism内部只提供了对Button的Click事件支持,其它的事件比如说TextBlock的Click事件,需要仿照Button的 Click事件来设计。

      2)一部分被MVP模型的Model所消化,如GridView的SelectionChanged事件。

      3)最后一部分,是实在不能转移的,就只好留在View中了,如该窗体的Loaded事件,这些事件大都有一个特性——它们都是基于控件的生命周期的。比如说窗体的Loaded事件,就是在窗体加载完成之后,别小看这个方法,只要没执行完,窗体中的数据和XAML就还都是未初始化过的,不能使用。这样的方法很多,但使用并不是很频繁,也就偶尔会用到,所以不必担心它们残留在View中而带来的一些麻烦:视觉不爽啦、没有重构彻底啦。要知道把事件转换为 Command是一个度的问题,过分设计往往会导致性能下降、开发周期变长等诸多问题。

      接下来的2篇文章《AttachedBehavior技术详解》和《包氏波动思想》将会分别介绍前两种情况。

    这里面蕴含着OO编程中组合(Composite)的思想。

      后来,随着“依赖反转”理念的深入,我们也可以进一步重构如下:

        public partial class Window1 : Window
        {
            private XXXHelper helper = new XXXHelper();
            public Window1()
            {
                InitializeComponent();
                helper.Window1 = this;
            }
            private void button1_Click(object sender, RoutedEventArgs e)
            {
                helper.UpdateContent();
            }
            public void UpdateContent(string content)
            {
                label1.Content = content;
            }
        }
        public class XXXHelper
        {
            public Window1 Window1 { get; set; }
            public void UpdateContent()
            {
                Window1.UpdateContent("Hello World");
            }
        }

      在新的重构中,我们在XXXHelper中也添加了一个对Window1的引用,这样就可以直接通过XXXHelper来改变Label的内容了。这可以被认为是MVP模式的前身,但就像辛亥革命那样,重构并不是很彻底,因为仍然要手动控制Label的显示内容。如果能只操作数据,UI就会自动跟着发生改变,这该有多好?遗憾的是,传统Winform和ASP.NET中的数据绑定做不到这一点,只有WPF和Silverlight中的绑定技术才能完美的诠释这一理念。

  • 相关阅读:
    Java 重载机制
    网关、DNS、子网掩码、MAC地址、路由器、猫
    前台?后台?前端?后端?
    JSP初学
    PS笔记
    Pandorabox等类OpenWrt的路由器 实现后端设备接入IPV6(中继代理+NAT)
    三星S5_G9008V 解锁联通4G(安卓6.0)
    一个意外的发现
    硬改路由器-MW310R-AR9341篇
    关于使用硬改的路由器的各种经历
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/1774951.html
Copyright © 2011-2022 走看看