.NET中的委托—事件机制: 办公室的故事
Chris Sells 著 ( <<ATL Internals>>一书作者之一,该书是ATL编程的宝典)
Jackeygou 译 研发中心
软件技术的动人美感来源于对现实世界的真实理解. 一— 译注
===========================================
强类型耦合
------------
从前在我们这个城市的西南角,有一家小技术服务公司,公司里有一位聪明能干的年轻人,他的名字叫Peter。不幸的是他的老板却是一位吝啬、多疑,而且极为循规蹈矩的小人,例如下属的任何工作都必须先报告,而且经他审批后才能进行。可怜的Peter自然不愿他的老板整日里站在自己的身后虎视眈眈,于是他对老板保证,自己的任何工作进度都会向他及时通禀。Peter实现这一承诺的方法就是周期性的利用类型引用回调boss,把他老板叫过来审查。程序实现如下:
class Worker {
public void Advise(Boss boss) { _boss = boss; }
public void DoWork() {
Console.WriteLine("Worker: work started");
if( _boss != null ) _boss.WorkStarted(); // 开始工作的审批
Console.WriteLine("Worker: work progressing");
if( _boss != null ) _boss.WorkProgressing(); // 进行工作的审批
Console.WriteLine("Worker: work completed");
if( _boss != null ) {
int grade = _boss.WorkCompleted(); // 完成工作的审批
Console.WriteLine("Worker grade= " + grade);
}
}
private Boss _boss;
}
class Boss {
public void WorkStarted() { /* 老板实际上并不很关心. */ }
public void WorkProgressing() { /*老板实际上并不很关心. */ }
public int WorkCompleted() {
Console.WriteLine("It's about time!");
return 2; /* 满分10分,才给2分,够吝啬小气吧. */
}
}
class Universe {
static void Main() {
Worker peter = new Worker(); // 生成peter实例
Boss boss = new Boss(); // 生成boss实例
peter.Advise(boss);
peter.DoWork();
Console.WriteLine("Main: 工作结束!");
Console.ReadLine();
}
}
【译注:以下是上段程序输出结果:
Worker: work started
Worker: work progressing
Worker: work completed
It's about time!
Worker grade = 2
Main: worker completed work
】
接口
----------
现在Peter已经成为一个特殊的成员,因为它不仅要忍受它那位吝啬老板的指使,而且还与Universe对象紧密相关(没办法谁让他身不逢时处于Universe类的Main函数中)。这种“亲密接触”使Peter觉得Universe对他的工作进度也很感兴趣。不幸的是,除了保证boss能够被通知外,如果不为Universe添加一个特殊的通知方法和回调,Peter无法向Universe通知其工作进度。而Peter首先要做的就是从具体的通知执行过程中分离出隐藏的通知约定,而这就需要定义接口了:
interface IWorkerEvents {
void WorkStarted();
void WorkProgressing();
int WorkCompleted();
}
class Worker {
public void Advise(IWorkerEvents events) { _events = events; }
public void DoWork() {
Console.WriteLine("Worker: work started");
if( _events != null ) _events.WorkStarted();
Console.WriteLine("Worker: work progressing");
if(_events != null ) _events.WorkProgressing();
Console.WriteLine("Worker: work completed");
if(_events != null ) {
int grade = _events.WorkCompleted();
Console.WriteLine("Worker grade= " + grade);
}
}
private IWorkerEvents _events;
}
class Boss : IWorkerEvents {
public void WorkStarted() { /* boss doesn't care. */ }
public void WorkProgressing() { /* boss doesn't care. */ }
public int WorkCompleted() {
Console.WriteLine("It's about time!");
return 3; /* out of 10 */
}
}
【译注:以下是上段程序输出结果:
Worker: work started
Worker: work progressing
Worker: work completed
It's about time!
Worker grade = 3
Main: worker completed work
】
委托
-----------
不幸的是,Peter整日忙于通知他的老板去负责执行接口,而无暇顾及通知Universe。但是Peter觉得这已经不远了,因为至少他已经成功的将对具体老板的引用,抽象为对统一的IWorkerEvents接口的引用了。换句话说,别的实现了IWorkerEvents接口的什么人都可以收到工作进度通知。
就这样,过了一段时间,Peter的老板依然对Peter很不满,他大声的抱怨到:“Hi! Peter,你知道不知道,你真的很烦呀!为什么你每次在开始工作和工作进行时都要叫上我,我并不是每次都对这些过程感兴趣。你不仅强迫我做一些我不感兴趣的事情,而且你还浪费了大量宝贵的工作时间在等我从审批事件中返回。如果我很忙,不能做出回答,难道你就要放假不成?你能不能够别这样来打搅我,OK?”
此时,Peter逐渐意识到采用接口在很多时候时是非常有用的(译注:接口在COM世界里,可以称得上是万物之本),但是用接口来处理事件时,就有些粒度(译注:不是力度)不够精细。他希望在事件发生时只通知哪些对该事件感兴趣的人,而不是让一个人必须对所有的事件感兴趣。所以,Peter将接口进一步肢解成更小的独立的委托函数,每一个委托函数可以看作是一个轻量级的函数接口,意义等价于类接口。
delegate