完整版本的办公室故事,转自发赛特社区

发布时间: 2007-05-21 09:45    作者: 未知    来源: 未知    浏览:    评论

.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 void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted();

class Worker {
public void DoWork() {
Console.WriteLine("Worker: work started");
if( started != null ) started();

Console.WriteLine("Worker: work progressing");
if( progressing != null ) progressing();

Console.WriteLine("Worker: work completed");
if( completed != null ) {
int grade = completed();
Console.WriteLine("Worker grade= " + grade);
}
}
public WorkStarted started;
public WorkProgressing progressing;
public WorkCompleted completed;
}

class Boss {
public int WorkCompleted() {
Console.WriteLine("Better...");
return 4; /* out of 10 */
}
}

class Universe {
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);
peter.DoWork();

Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
【译注:以下是上段程序输出结果:
Worker: work started
Worker: work progressing
Worker: work completed
Better...
Worker grade = 4
Main: worker completed work

【译注:对“但是用接口来处理事件时,就有些粒度不够精细。”的理解可用下例说明,请仔细观察一下程序,如果全部换作接口定义去处理,思考一下这样做的不利之处。欢迎大家讨论:
using System;
interface IWorkStartedEvent
{
void WorkStarted();
}
interface IWorkProgressingEvent
{
void WorkProgressing();
}
interface IWorkCompletedEvent
{
int WorkCompleted();
}

class Worker
{
public void Advise(IWorkCompletedEvent AEvent)
{
_event = AEvent;
}

public void DoWork()
{
Console.WriteLine("Worker: work completed");
if(_event != null )
{
int grade = _event.WorkCompleted();
Console.WriteLine("Worker grade = " + grade);
}
}
private IWorkCompletedEvent _event;
}

class Boss : IWorkCompletedEvent
{
public int WorkCompleted()
{
Console.WriteLine("Better...");
return 4; /* out of 10 */
}
}

class Universe
{
static void Main()
{
Worker peter = new Worker();
Boss boss = new Boss();
peter.Advise(boss);
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
以下是上段程序输出结果:
Worker: work completed
Better...
Worker grade = 4
Main: worker completed work


静态响应函数
---------------
这样一来,果然立竿见影。Peter再也不会因为开始工作审批等事件而去打搅他的老板。但是现在的问题是Peter依然没有将Universe列入他的事件响应者名单。因为Universe是一个全封闭实体类(all-compassing entity),所以,如果需要与委托相连,就要实例化Universe,实在没必要(设想一下Universe的多个实例需要多少资源…)。取而代之,采用静态函数实现。因为委托支持这些静态函数定义:
class Universe {
static void WorkerStartedWork() {
Console.WriteLine("Universe notices worker starting work");
}

static int WorkerCompletedWork() {
Console.WriteLine("Universe pleased with worker's work");
return 7;
}

static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed = new WorkCompleted(boss.WorkCompleted);// #
peter.started = new WorkStarted(Universe.WorkerStartedWork);
peter.completed = new WorkCompleted(Universe.WorkerCompletedWork); // 这一行使得前面#行白费了!boss被Universe取代了,也许Peter更愿意如此。
peter.DoWork();

Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
【译注:以下是上段程序输出结果:
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Universe pleased with worker's work
Worker grade = 7
Main: worker completed work


事件
----------
事与愿违,虽然Peter很乐意让Universe参与合作,但Universe自身很忙也不习惯将自己的全部身心都投入到单一的Peter实例中,以取代Peter老板委托的位置。此外Peter类中的委托字段是公有访问权限也会存在一些未知的负面作用。例如,那天Peter的老板想不开,突然决定要亲自激活Peter的委托任务,也尚未可知。(按此人的多疑易怒的性情是很有可能的!)
// Peter的老板自己将委托任务激活,这就是公有权限的恶果!!!
if( peter.completed != null ) peter.completed();
可怜的Peter自然不愿意上述的任何一种情况发生,他需要实现对于每一个委托都能加入注册和反注册函数,只有这样事件响应者可以添加和删除自身, 但谁都不能够清空整个事件列表,避免出现刚才Universe将boss取而代之的结果。Peter自身对此是无能为力了,但是通过C#提供的“event”关键字,Peter就可以梦想成真了:
class Worker {
...
public event WorkStarted started;
public event WorkProgressing progressing;
public event WorkCompleted completed;
}
Peter知道利用C#的委托—事件机制可以轻松搞定,而且这时event关键字提供了一个关于委托的属性(property),可以允许C#客户方便地通过“+=”和“-= ”添加和删除他们自定义的执行函数,而不像委托那样挂上就甩不掉:
static void Main() {
Worker peter = new Worker();
Boss boss = new Boss();
peter.completed += new WorkCompleted(boss.WorkCompleted);
peter.started += new WorkStarted(Universe.WorkerStartedWork);
peter.completed += new WorkCompleted(Universe.WorkerCompletedWork);
peter.DoWork();

Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
【译注:以下是上段程序输出结果
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Better...// 【译注:boss也通知到啦,刚才打‘#’那代码被执行了。但是且慢,boss打的那4分没有得到,后面只得到了Universe给的7分】
Universe pleased with worker's work
Worker grade = 7
Main: worker completed work


查询所有结果
--------------
对于这一点,Peter满怀信心。他可以方便管理所有与事件相关的执行者信息,而不需要与具体的执行者产生紧耦合关系。Peter注意到他的最终工作评定(completed事件)关联了两个事件执行函数:boss和Universe。他们都给Peter一个评定分数4分和7分。正是春风得意的Peter自然不会随意丢掉任何一个结果,他希望能得到每一个响应者的评分结果。因此,他决定提取委托调用列表,以便手工分别调用它们并累加,这样他的Money….
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
int grade = 0;
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
grade += wc();
Console.WriteLine("Worker grade= " + grade);
}
}
}
【译注:以下是上段程序输出结果
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Better...
Worker grade = 4 【译注:boss打的4分也得到啦】
Universe pleased with worker's work
Worker grade = 11【译注:Peter共得了11分!】
Main: worker completed work


异步通知:激活和放弃
------------------------
Peter很快又发现了新的问题,他的老板和Universe有时都会出现正为别的事忙碌而无暇顾及Peter的工作评定,这就使得Peter的工作评定时间被拖延了:
class Boss {
public int WorkCompleted() {
System.Threading.Thread.Sleep(3000); // 延时3秒
Console.WriteLine("Better..."); return 6; /* out of 10 */
}
}

class Universe {
static int WorkerCompletedWork() {
System.Threading.Thread.Sleep(4000); // 延时4秒
Console.WriteLine("Universe is pleased with worker's work");
return 7;
}
...
}
噢!看到了,这确实很容易造成时间的浪费,而且最让Peter恼火的是,周末他与Kristin的约会都被搅黄了。不行,这一定要改正!于是Peter决定放弃工作评定打分而改为异步委托—事件激活:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() )
{
wc.BeginInvoke(null, null);
}
}
}
【译注:以下是上段程序输出结果
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Main: worker completed work //【译注:由于是异步触发事件,因此这一行先输出啦】
Better... //【译注:评分已被忽略】
Universe pleased with worker's work //【译注:评分已被忽略】


异步通知:缓存池(Polling)
-------------------------------
这就使得Peter可以通知监听者的同时自己也能立即返回工作,让进程的线程池调用委托。然而不久他就发现监听者对其工作的评分丢掉了。这还了得!没有评定分数,我的绩效…Peter简直欲哭无泪.他立即采用缓存池(Polling)机制。Peter知道他做了一件明智的事并乐意Universe作为一个整体(不单单是他的boss)评判他。因此,Peter异步触发事件,但定期轮询,以察看可以获得的评分:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
IAsyncResult res = wc.BeginInvoke(null, null);
while( !res.IsCompleted ) System.Threading.Thread.Sleep(1);
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}
}
}
【译注:以下是上段程序输出结果
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Better...
Worker grade = 6
Universe pleased with worker's work
Worker grade = 7
Main: worker completed work //【译注:注意这个结果到最后才输出,下一节首句意思即是如此】


异步通知: 委托
----------------
不幸的是,Peter又倒退了—就象他一开始想避免boss站在一旁边监视他一样。也就是说,他现在要监看整个工作过程。因此,peter决定使用自己的委托作为异步委托完成时的通知方式,这样他就可以立即回去工作,而当工作被打分时,仍然可以接到通知:
public void DoWork() {
...
Console.WriteLine("Worker: work completed");
if( completed != null ) {
foreach( WorkCompleted wc in completed.GetInvocationList() ) {
wc.BeginInvoke(new AsyncCallback(WorkGraded), wc);
}
}
}

private void WorkGraded(IAsyncResult res) {
WorkCompleted wc = (WorkCompleted)res.AsyncState;
int grade = wc.EndInvoke(res);
Console.WriteLine("Worker grade= " + grade);
}
【译注:以下是上段程序输出结果
Worker: work started
Universe notices worker starting work
Worker: work progressing
Worker: work completed
Main: worker completed work //【译注:异步委托发生了效果,因此这一行先输出啦】
Better...
Worker grade = 6
Universe pleased with worker's work
Worker grade = 7


圆满结局
-----------
Peter、他的老板和Universe最终皆大欢喜,Peter的老板和Universe分别负责他们各自感兴趣的事件。这就减轻了执行负担和不必要的来回调用时间。Peter负责在发生事件时通知与事件关联的执行者,并最终异步获得结果,而不管他们做出回应时间的长短。但是,Peter也深知这一切并不像外表看起来那么简单,因为当他异步激活一个事件,与该事件关联的执行函数很可能是在另一个线程中执行,由此造成潜在的调度处理问题。不过Peter与Mike是好朋友,而Mike则精通线程问题的处理,他会为Peter不遗余力的。

现在一切OK!,关于Peter的故事就讲到这里,如果大家感兴趣的话,我还有很多故事要讲的!

TAG

Smile Big Smile Surprise Stick out tongue Wink Sad Tongue Tied Indifferent Crying Embarrassed Cool Angry Angel Devil [8-|] [:#] [:-*] [:^)] [<:o)] [|-)] Yes Beer Left Hug Music Star Time Snail Pizza Automobile Umbrella Computer Storm [mo] [8o|] [^o)] [+o(] [*-)] [8-)] Coffee No Drinks [Z] Right Hug Cake Broken Heart Gift Wilted Flower Movie Dog Idea Sleep Email Travel Paradise
呢称:

加粗 斜体 下划线 链接 图片 代码 邮件地址 引用 列表

最多只能输入100个字符

Tags

SQL 数据库 asp.net C# XML 控件 .NET教程 程序 事件 数据 安全 代码 Server 客户端 验证 数据库专栏 接口 文件 Oracle DataSet 函数 DataGrid 问题 .net return C#语言 JavaScript 服务 IIS 对象 语句 windows 继承 时间 web.config 设计 开发 参数 变量 解决 字符 ADO.net 环境 VB.Net语言 web 异常 工具 服务器 计算 实例 OLEDB Application VB Word WebService insert asp net 安装 记录

精华推荐

更多

精品下载

更多