zoukankan      html  css  js  c++  java
  • .NET 事件模型教程(三)

    通过前两节的学习,你已经掌握了 .NET 事件模型的原理和实现方式。这一节我将介绍两个替代方案,这些方案并不是推荐采用的,请尽量采用事件模型去实现。另外,在本节末尾,有一段适合熟悉 Java 语言的读者阅读,讨论了 .NET 和 Java 在“事件模型”方面的差异。

    目录

    使用接口实现回调

    事件模型其实是回调函数的一种特例。像前面的例子,Form1 调用了 Worker,Worker 反过来(通过事件模型)让 Form1 改变了状态栏的信息。这个操作就属于回调的一种。

    在“.NET Framework 类库设计指南”中提到了:“委托、接口和事件允许提供回调功能。每个类型都有自己特定的使用特性,使其更适合特定的情况。”(参见本地 SDK 版本在线 MSDN 版本

    事件模型中,事实上也应用了委托来实现回调,可以说,事件模型是委托回调的一个特例。如果有机会,我会在关于多线程的教程中介绍委托回调在多线程中的应用。

    这里我先来看看,如何使用接口实现回调功能,以达到前面事件模型实现的效果。

    Demo 1I:使用接口实现回调。

    using System;
    using System.Threading;
    using System.Collections;
    
    namespace percyboy.EventModelDemo.Demo1I
    {
        // 注意这个接口
        public interface IWorkerReport
        {
            void OnStartWork(int totalUnits);
            void OnEndWork();
            void OnRateReport(double rate);
        }
    
        public class Worker
        {
            private const int MAX = Consts.MAX;
            private IWorkerReport report = null;
    
            public Worker()
            {
            }
    
            // 初始化时同时指定 IWorkerReport
            public Worker(IWorkerReport report)
            {
                this.report = report;
            }
    
            // 或者初始化后,通过设置此属性指定
            public IWorkerReport Report
            {
                set { report = value; }
            }
    
            public void DoLongTimeTask()
            {
                int i;
                bool t = false;
                double rate;
    
                if (report != null)
                {
                    report.OnStartWork( MAX );
                }
    
                for (i = 0; i <= MAX; i++)
                {
                    Thread.Sleep(1);
                    t = !t;
                    rate = (double)i / (double)MAX;
    
                    if (report != null)
                    {
                        report.OnRateReport( rate );
                    }
                }
    
                if ( report != null)
                {
                    report.OnEndWork();
                }
    
            }
        }
    }
    

    你可以运行编译好的示例,它可以完成和前面介绍的事件模型一样的工作,并保证了耦合度没有增加。调用 Worker 的 Form1 需要做一个 IWorkerReport 的实现:

            private void button1_Click(object sender, System.EventArgs e)
            {
                statusBar1.Text = "开始工作 ....";
                this.Cursor = Cursors.WaitCursor;
    
                long tick = DateTime.Now.Ticks;
    
                Worker worker = new Worker();
    
                // 指定 IWorkerReport
                worker.Report = new MyWorkerReport(this);
    
                worker.DoLongTimeTask();
    
                tick = DateTime.Now.Ticks - tick;
                TimeSpan ts = new TimeSpan(tick);
    
                this.Cursor = Cursors.Default;
                statusBar1.Text = String.Format("任务完成,耗时 {0} 秒。", ts.TotalSeconds);
            }
    
            // 这里实现 IWorkerReport
            private class MyWorkerReport : IWorkerReport
            {
                public void OnStartWork(int totalUnits)
                {
                }
    
                public void OnEndWork()
                {
                }
    
                public void OnRateReport(double rate)
                {
                    parent.statusBar1.Text = String.Format("已完成 {0:P0} ....", rate);
                }
    
                private Form1 parent;
    
                public MyWorkerReport(Form1 form)
                {
                    this.parent = form;
                }
            }
    

    你或许已经觉得这种实现方式,虽然 Worker 类“里面”可能少了一些代码,却在调用时增加了很多代码量。从重复使用的角度来看,事件模型显然要更方便调用。另外,从面向对象的角度,我觉得理解了事件模型的原理之后,你会觉得“事件”会更亲切一些。

    另外,IWorkerReport 中包含多个方法,而大多时候我们并不是每个方法都需要,就像上面的例子中那样,OnStartWork 和 OnEndWork 这两个都是空白。如果接口中的方法很多,也会给调用方增加更多的代码量。

    下载的源代码中还包括一个 Demo 1J,它和 Worker 类一起,提供了一个 IWorkerReport 的默认实现 WorkerReportAdapter(每个方法都是空白)。这样,调用方只需要从 WorkerReportAdapter 继承,重写其中需要重写的方法,这样会减少一部分代码量。但我觉得仍然是很多。

    注意,上述的代码,套用(仅仅是套用,因为它不是事件模型)“单播事件”和“多播事件”的概念来说,它只能支持“单播事件”。如果你想支持“多播事 件”,我想你可以考虑加入 AddWorkerReport 和 RemoveWorkerReport 方法,并使用 Hashtable 等数据结构,存储每一个加入的 IWorkerReport。

    [TOP]

    .NET 事件模型和 Java 事件模型的对比

    (我对 Java 语言的了解不是很多,如果有误,欢迎指正!)

    .NET 的事件模型,对于 C#/VB.NET 两种主流语言来说,是在语言层次上实现的。C# 提供了 event 关键字,VB.NET 提供了 Event,RaiseEvent 关键字。像前面两节所讲的那样,它们都有各自的声明事件成员的语法。而 Java 语言本身是没有“事件”这一概念的。

    从面向对象理论来看,.NET 的一个类(或类的实例:对象),可以拥有:字段、属性、方法、事件、构造函数、析构函数、运算符等成员类型。在 Java 中,类只有:字段、方法、构造函数、析构函数、运算符。Java 的类中没有属性和事件的概念。(虽然 Java Bean 中将 getWidth、setWidth 的两个方法,间接的转换为一个 Width 属性,但 Java 依然没有把“属性”作为一个语言层次的概念提出。)总之,在语言层次上,Java 不支持事件。

    Java Swing 是 Java 世界中常用的制作 Windows 窗体程序的一套 API。在 Java Swing 中有一套事件模型,来让它的控件(比如 Button 等)拥有事件机制。

    Swing 事件模型,有些类似于本节中介绍的接口机制。它使用的接口,诸如 ActionListener、KeyListener、MouseListener(注意:按照 Java 的命名习惯,接口命名不用前缀 I)等;它同时也提供一些接口的默认实现,如 KeyAdapter,MouseAdapter 等,使用方法大概和本节介绍的类似,它使用的是 addActionListener/removeActionListener,addKeyListener /removeKeyListener,addMouseListener/removeMouseListener 等方法,来增减这些接口的。

    正像本节的例子那样,使用接口机制的 Swing 事件模型,需要书写很多的代码去实现接口或者重写 Adapter。而相比之下,.NET 事件模型则显得更为轻量级,所需的挂接代码仅一行足矣。

    另一方面,我们看到 Swing 的命名方式,将这些接口都命名为 Listener,监听器;而相比之下,.NET 事件模型中,对事件的处理被称为 handler,事件处理程序。一个采用“监听”,一个是“处理”,我认为这体现了一种思维上的差异。

    还拿张三大叫的例子来讲,“处理”模型是说:当张三大叫事件发生时,外界对它做出处理动作(handle this event);监听,则是外界一直“监听”着张三的一举一动(listening),一旦张三大叫,监听器就被触发。处理模型是以张三为中心的思维,监听 模型则是以外部环境为中心的思维。

  • 相关阅读:
    vue2.0阻止事件冒泡
    IconFont 图标制作和使用
    Gulp入门教程
    伪类实现特殊图形,一个span加三角形
    Vue渲染列表,在更新data属性后,列表未更新问题
    理解Array.prototype.slice.call(arguments)
    ;(function(){ //代码})(); 自执行函数开头为什么要加;或者!
    Hexo 搭建博客 本地运行 常见报错及解决办法
    说说JSON和JSONP,也许你会豁然开朗
    数组去重的常用方法
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/2662976.html
Copyright © 2011-2022 走看看