[转].Net 事件模型教程(三)

 原文地址:http://blog.joycode.com/percyboy/archive/2005/01/22/43436.aspx

 

使用接口实现回调

事件模型其实是回调函数的一种特例。像前面的例子,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。

 

.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),一旦张三大叫,监听器就被触发。处理模型是以张三为中心的思维,监听模型则是以外部环境为中心的思维。

你可能感兴趣的:([转].Net 事件模型教程(三))