繁体中文
设为首页
加入收藏
当前位置:.Net技术首页 >> Asp.Net开发 >> 完整版本的办公室故事,转自发赛特社区

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

2007-08-15 08:00:00  作者:  来源:互联网  浏览次数:0  文字大小:【】【】【
简介:.NET中的委托—事件机制: 办公室的故事 Chris Sells 著 ( 一书作者之一,该书是ATL编程的宝典) Jackeygou 译 研发中心 软件技术的动人美感来源于对现实世界的真实理解. 一— 译注 =============================...

.NET中的委托—事件机制: 办公室的故事

Chris Sells 著 ( <>一书作者之一,该书是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的故事就讲到这里,如果大家感兴趣的话,我还有很多故事要讲的!

责任编辑:admin
相关文章