【譯注:C#進階文章。Chris Sells是《ATL Internals》一書作者之一。譯文中所有程 序調試環境均為Microsoft Visual Studio.NET 7.0 Beta2和 Microsoft .Net Framewo rk SDK Beta2。代碼就是文章,請仔細閱讀代碼J】 類型耦合 從前,在南方的一個異國他鄉,有一個叫peter的勤勞的工人。他對boss百依百順,但他 的boss卻是個卑鄙無信的小人,他堅持要求peter不斷匯報工作情況。由於peter不希望 被boss盯著干活,於是他向boss承諾隨時匯報工作進度。peter利用類型引用定期回調b oss來實現這個承諾: using System;//【譯注:譯者補充】 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() { /*boss不關心. */ } public void WorkProgressing() { /*boss不關心. */ } public int WorkCompleted() { Console.WriteLine("It's about time!"); return 2; /* 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 started Worker: work progressing Worker: work completed It's about time! Worker grade = 2 Main: worker completed work 】*/ 接口 現在,peter成了一個特殊人物,他不但能夠忍受卑鄙的boss,和universe也建立 了緊密的聯系。peter感到universe對他的工作進程同樣感興趣。不幸的是,除了保證b oss能夠被通知外,如果不為universe添加一個特殊的通知方法和回調,peter無法向un iverse通知其工作進程。Peter希望能從那些通知方法的實現中分離出潛在的通知約定, 為此,他決定將方法剝離到接口中: using System; //【譯注:譯者補充】 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 //【譯注:Boss實現該接口】 { public void WorkStarted(){ /*boss不關心. */ } public void WorkProgressing(){ /*boss不關心. */ } public int WorkCompleted() { Console.WriteLine("It's about time!"); return 3; /* out of 10 */ } } class Universe { static void Main() { Worker peter = new Worker(); Boss boss = new Boss(); peter.Advise(boss); //【譯注:或peter.Advise((IWorkerEvents)boss); 】 peter.DoWork(); Console.WriteLine("Main: worker completed work"); Console.ReadLine(); } } /*【譯注:以下是上段程序輸出結果: Worker: work started Worker: work progressing Worker: work completed It's about time! Worker grade = 3 Main: worker completed work 】*/ 委托 不幸的是,由於peter忙於通知boss實現這個接口,以至於沒有顧得上通知univer se也實現該接口,但他知道不久就需如此,至少,他已經抽象了對boss的引用,因此, 別的實現了IworkerEvents接口的什麼人都可以收到工作進度通知。【譯注:請參見上一 節代碼示例及譯注】 然而,peter的boss依然極度不滿,“Peter!”boss咆哮者,“你為什麼要通知我 什麼時候開始工作、什麼時候正在進行工作?我不關心這些事件,你不但強迫我實現這 些方法,你還浪費了你的寶貴的工作時間等我從事件中返回。當我實現的方法需占用很 長時間時,你等我的時間也要大大延長!你難道不能想想別的辦法不要老是來煩我嗎? ” 此時,peter意識到盡管在很多情況下接口很有用,但在處理事件時,接口的粒度 還不夠精細。他還要能做到僅僅通知監聽者真正感興趣的事件。因此,peter決定把接口 裡的方法肢解成若干個獨立的委托函數,每一個都好象是只有一個方法的小接口。 using System; //【譯注:譯者補充】 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 WorkStarted started = null;】 public WorkProgressing progressing; //【譯注:這樣寫更規矩:public Work Progressing progressing = null;】 public WorkCompleted completed; //【譯注:這樣寫更規矩:public WorkComp leted completed = null;】 } 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 】 */ 【譯注:對“但在處理事件時,接口的粒度還不夠精細”的理解可用下例說明,請仔細 觀察一下程序,思考一下這樣做的不利之處J 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 */ 】 靜態監聽者 這就達到了不用boss不關心的事件去煩他的目標。但是,peter還是不能夠使univ erse成為其監聽者。因為universe是一個全封閉的實體,所以將委托掛鉤在universe的 實例上不妥的(設想一下Universe的多個實例需要多少資源...)。peter意識到應將委 托掛鉤於universe的靜態成員上,因為委托也完全適應於靜態成員: using System; 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 = null; public WorkProgressing progressing = null; public WorkCompleted completed = null; } class Boss { public int WorkCompleted() { Console.WriteLine("Better..."); return 4; /* out of 10 */ } } //【譯注:以上代碼為譯者補充】 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);// 【譯注:這一行代碼使得“×”那一行代碼白做了L】 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 】*/ 事件 不幸的是,universe現在變得太忙並且不習慣於注意某一個人—universe用自己的 委托取代了peter的boss的委托,這顯然是將Worker類的委托字段設為public的意外的副 作用。【譯注:請參見上節例子代碼及譯注】同樣地,如果peter的boss不耐煩了,他自 己就可以觸發peter的委托(peter的boss可是有暴力傾向的) // peter的boss自己動手了 if( peter.completed != null ) peter.completed(); peter希望確保不會發生這兩種情況。他意識到必須為每一個委托加入注冊和反注冊函數 ,這樣監聽者就可以添加或移去它們,但誰都不能夠清空整個事件列表。peter自己沒去 實現這些方法,相反,他使用event關鍵字讓C#編譯器幫他達到這個目的: class Worker { //... public event WorkStarted started; public event WorkProgressing progressing; public event WorkCompleted completed; } peter懂得關鍵字event使得委托具有這樣的特性:只允許C#客戶用+=或-=操作符添 加或移去它們自己,這樣就迫使boss和universe舉止文雅一些: 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(); } 【譯注:以下是完整代碼: using System; 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 event WorkStarted started ; public event WorkProgressing progressing; public event WorkCompleted completed; } class Boss { public int WorkCompleted() { Console.WriteLine("Better..."); return 4; /* out of 10 */ } } 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); 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也通知到啦J“√”那一行代碼有用啦J,但是且慢,boss打 的那4分沒有得到,後面只得到了Universe給的7分L】 Universe pleased with worker's work Worker grade = 7 Main: worker completed work */ 】