類似這樣的例子是很多的,GOF對它進行了抽象,稱為Observer設計模式:Observer設計模式是為了定義對象間的一種一對多的依賴關係,以便於當一個對象的狀態改變時,其他依賴於它的對象會被自動告知並更新。Observer模式是一種松耦合的設計模式。 實現範例的Observer設計模式我們之前已經對委託和事件介紹很多了,現在寫代碼應該很容易了,現在在這裡直接給出代碼,並在注釋中加以說明。 using System; using System.Collections.Generic; using System.Text;namespace Delegate { // 熱水器 public class Heater { private int temperature; public delegate void BoilHandler(int param); //聲明委託 public event BoilHandler BoilEvent; //聲明事件 // 燒水 public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) { if (BoilEvent != null) { //如果有對象註冊 BoilEvent(temperature); //調用所有註冊對象的方法 } } } } } // 警報器 public class Alarm { public void MakeAlert(int param) { Console.WriteLine("Alarm:嘀嘀嘀,水已經 {0} 度了:", param); } } // 顯示器 public class Display { public static void ShowMsg(int param) { //靜態方法 Console.WriteLine("Display:水快燒開了,當前溫度:{0}度。", param); } } class Program { static void Main() { Heater heater = new Heater(); Alarm alarm = new Alarm(); heater.BoilEvent += alarm.MakeAlert; //註冊方法 heater.BoilEvent += (new Alarm()).MakeAlert; //給匿名對象註冊方法 heater.BoilEvent += Display.ShowMsg; //註冊靜態方法 heater.BoilWater(); //燒水,會自動調用註冊過對象的方法 } } } 輸出為: Alarm:嘀嘀嘀,水已經 96 度了: Alarm:嘀嘀嘀,水已經 96 度了: Display:水快燒開了,當前溫度:96度。 // 省略... .Net Framework中的委託與事件儘管上面的範例很好地完成了我們想要完成的工作,但是我們不僅疑惑:為什麼.Net Framework 中的事件模型和上面的不同?為什麼有很多的EventArgs參數? 在回答上面的問題之前,我們先搞懂 .Net Framework的編碼規範:
- 委託類型的名稱都應該以EventHandler結束。
- 委託的原型定義:有一個void傳回值,並接受兩個輸入參數:一個Object 類型,一個 EventArgs類型(或繼承自EventArgs)。
- 事件的命名為 委託去掉 EventHandler之後剩餘的部分。
- 繼承自EventArgs的類型應該以EventArgs結尾。
再做一下說明:
- 委託聲明原型中的Object類型的參數代表了Subject,也就是監視對象,在本例中是 Heater(熱水器)。回呼函數(比如Alarm的MakeAlert)可以通過它訪問觸發事件的對象(Heater)。
- EventArgs 對象包含了Observer所感興趣的資料,在本例中是temperature。
上面這些其實不僅僅是為了編碼規範而已,這樣也使得程式有更大的靈活性。比如說,如果我們不光想獲得熱水器的溫度,還想在Observer端(警報器或者顯示器)方法中獲得它的生產日期、型號、價格,那麼委託和方法的聲明都會變得很麻煩,而如果我們將熱水器的引用傳給警報器的方法,就可以在方法中直接存取熱水器了。 現在我們改寫之前的範例,讓它符合 .Net Framework 的規範: using System; using System.Collections.Generic; using System.Text;
namespace Delegate { // 熱水器 public class Heater { private int temperature; public string type = "RealFire 001"; // 添加型號作為示範 public string area = "China Xian"; // 添加產地作為示範 //聲明委託 public delegate void BoiledEventHandler(Object sender, BoiledEventArgs e); public event BoiledEventHandler Boiled; //聲明事件
// 定義BoiledEventArgs類,傳遞給Observer所感興趣的資訊 public class BoiledEventArgs : EventArgs { public readonly int temperature; public BoiledEventArgs(int temperature) { this.temperature = temperature; } }
// 可以供繼承自 Heater 的類重寫,以便繼承類拒絕其他對象對它的監視 protected virtual void OnBoiled(BoiledEventArgs e) { if (Boiled != null) { // 如果有對象註冊 Boiled(this, e); // 調用所有註冊對象的方法 } }
// 燒水。 public void BoilWater() { for (int i = 0; i <= 100; i++) { temperature = i; if (temperature > 95) { //建立BoiledEventArgs 對象。 BoiledEventArgs e = new BoiledEventArgs(temperature); OnBoiled(e); // 調用 OnBolied方法 } } } }
// 警報器 public class Alarm { public void MakeAlert(Object sender, Heater.BoiledEventArgs e) { Heater heater = (Heater)sender; //這裡是不是很熟悉呢? //訪問 sender 中的公用欄位 Console.WriteLine("Alarm:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Alarm: 嘀嘀嘀,水已經 {0} 度了:", e.temperature); Console.WriteLine(); } }
// 顯示器 public class Display { public static void ShowMsg(Object sender, Heater.BoiledEventArgs e) { //靜態方法 Heater heater = (Heater)sender; Console.WriteLine("Display:{0} - {1}: ", heater.area, heater.type); Console.WriteLine("Display:水快燒開了,當前溫度:{0}度。", e.temperature); Console.WriteLine(); } }
class Program { static void Main() { Heater heater = new Heater(); Alarm alarm = new Alarm();
heater.Boiled += alarm.MakeAlert; //註冊方法 heater.Boiled += (new Alarm()).MakeAlert; //給匿名對象註冊方法 heater.Boiled += new Heater.BoiledEventHandler(alarm.MakeAlert); //也可以這麼註冊 heater.Boiled += Display.ShowMsg; //註冊靜態方法
heater.BoilWater(); //燒水,會自動調用註冊過對象的方法 } } }
輸出為: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已經 96 度了: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已經 96 度了: Alarm:China Xian - RealFire 001: Alarm: 嘀嘀嘀,水已經 96 度了: Display:China Xian - RealFire 001: Display:水快燒開了,當前溫度:96度。 // 省略 ... 總結 在本文中我首先通過一個GreetingPeople的小程式向大家介紹了委託的概念、委託用來做什麼,隨後又引出了事件,接著對委託與事件所產生的中間代碼做了粗略的講述。 在第二個稍微複雜點的熱水器的範例中,我向大家簡要介紹了 Observer設計模式,並通過實現這個範例完成了該模式,隨後講述了.Net Framework中委託、事件的實現方式。 例子: using System; using System.Collections.Generic; using System.Text; namespace Delegate { /* * 老鼠出來找食物,正在搶食物時,其中一隻老鼠突然發現了一隻貓,於是叫了一聲:貓來了,大家快跑. * 老鼠的叫聲,引起了貓的注意.貓看到這麼多美食,太開心了,於是也叫了一聲. * 貓的叫聲,吵醒了熟睡中的主人.於是主人說話了,並過去瞧個究竟. */ class Mouse { public delegate void MouseDelegate(Object sender, MouseEventArgs e);//聲明委託 public event MouseDelegate callEventHandler;//聲明事件 private bool seeCat = false;//是否發現貓 public class MouseEventArgs : EventArgs { public int mouseNumber = 10; public MouseEventArgs(int number) { this.mouseNumber = number; } } public void Called(Object sender, MouseEventArgs e) { Console.WriteLine("老鼠驚叫:我發現貓來了,大家快跑...,於是 {0} 個老鼠一起抱頭鼠竄",e.mouseNumber); Console.WriteLine("---------- 以上是老鼠的動作 -----------"); } public void FindFood() { for(int i=10;i<24;i++) { if(i==18) { Console.WriteLine("晚上18點,老鼠出來行走了...."); } if (i == 20) { Console.WriteLine("晚上20點,老鼠正找食物...."); } if (i == 21) { Console.WriteLine("晚上21點,老鼠找到食物,開始美美的享受美餐...."); } if (i == 22) { Console.WriteLine("晚上22點,老鼠越來越多,食物不夠分,引起轟搶...."); } if (i == 23) { Console.WriteLine("正當老鼠搶得不可開交時,突然有一隻老鼠發現了一隻貓...."); seeCat = true; break; } } if (seeCat) { MouseEventArgs e = new MouseEventArgs(10); SeeCat(e); } } public virtual void SeeCat(MouseEventArgs e) { if (callEventHandler != null) { callEventHandler(this, e); } } } class Cat { public void Hear(Object sender, Mouse.MouseEventArgs e) { Console.WriteLine("貓心裡想:我聽到老鼠的叫聲了.嘿嘿,美餐來了!"); Walking(e); } private void Walking(Mouse.MouseEventArgs e) { Console.WriteLine("貓飛快地朝老鼠發出聲音的方向走去..."); See(e); } private void See(Mouse.MouseEventArgs e) { Console.WriteLine("貓看到老鼠了."); Called(e); } private void Called(Mouse.MouseEventArgs e) { Console.WriteLine("貓欣喜若狂地說:哇!這麼多隻老鼠,我數數...天啊!一共有 {0} 只老鼠...", e.mouseNumber); Console.WriteLine("---------- 以上是貓的動作 -----------"); } } class Human { public void Hear(Object sender, Mouse.MouseEventArgs e) { Called(); } private void Called() { Console.WriteLine("主人說:這隻死貓,還讓不讓人睡覺!看看去,到底是怎麼了?"); Walking(); } private void Walking() { Console.WriteLine("go go go !"); Console.WriteLine("---------- 以上是人的動作 -----------"); } } class Program { static void Main() { Mouse m = new Mouse(); m.callEventHandler += m.Called; Cat c = new Cat(); m.callEventHandler += c.Hear; Human man = new Human(); m.callEventHandler += man.Hear; m.FindFood(); string a = Console.Read().ToString(); } } } |