事件的记忆碎片
前言
?
委托類型的實例是存儲著一個方法,并通過委托來調用那個方法,但是委托還有其他的用途。
先講一個模式:publish-subscribe(訂閱-發(fā)布)
它是應對這樣的一個場景情形:就是把單一事件的通知廣播給多個訂閱者。
?
這句話通俗一點講的話就是:
現(xiàn)在有方法A、B、C、D、E,自己想調用這五個方法中的全部或者部分方法。但是又不想一個一個顯式的去調用,因為如果方法很多的話就會形成一個代碼的堆砌,不夠簡潔,時間一長也不好維護。這時候有一個想法就是能有一個“方法F”來收集自己想要調用的方法就好了,最后自己只要調用“方法F”就可以調用所有自己想要調用的方法。
到這邊有人可能就感覺不是很自然,感覺有點陌生,沒有關系,我也是和你一樣很陌生,本文就是幫助你對publish-subscribe(訂閱-發(fā)布)這個模式的熟悉并且完全掌握。
好的,publish-subscribe(訂閱-發(fā)布)先講到這邊。本文的第一句話就提到委托還有其他的用途。具體是指:一個委托變量可以引用一系列委托,在這一系列委托中,每一個委托都會順序指向一個后續(xù)的委托,從而形成一個委托鏈。只要調用這個委托的方法對象,在這個委托鏈上的所有方法就會按照委托鏈的順序一一執(zhí)行。我們在這邊可以做一個猜想:委托變量可以調用多個方法,是不是“方法F”就是用委托變量來實現(xiàn)的呢? 其實答案就是這樣。
?
具體的場景描述:
來考慮一個溫度控制的例子。一個加熱器和一個冷卻器連接到同一個自動調溫器。為了控制加熱器和冷卻器的打開和關閉,要向他們通知溫度的變化。自動調溫器將溫度的變化發(fā)布給多個訂閱者----也就是加熱器和冷卻器。
?
一、定義訂閱者的方法
public class Cooler
??? {
??????? public Cooler(float temperature)
??????? {
??????????? Temperature = temperature;
??????? }
??????? public float Temperature { get; set; }
??????? public void OnTemperatureChanged(float newTemperature)
??????? {
??????????? if (newTemperature > Temperature)
??????????? {
??????????????? Console.WriteLine("Cooler:ON");
??????????? }
??????????? else
??????????? {
??????????????? Console.WriteLine("Cooler:Off");
??????????? }
??????? }
}
??? public class Heater
??? {
??????? public Heater(float temperature)
??????? {
??????????? Temperature = temperature;
??????? }
??????? public float Temperature { get; set; }
??????? public void OnTemperatureChanged(float newTemperature)
??????? {
??????????? if (newTemperature < Temperature)
??????????? {
?? ?????????????Console.WriteLine("Heater:ON");
??????????? }
??????????? else
??????????? {
??????????????? Console.WriteLine("Heater:Off");
??????????? }
??????? }
}
這兩個類幾乎完全一致,兩個類都提供了一個OnTemperatureChanged方法,調用OnTemperatureChanged就是為了向Heater和Cooler指出溫度變化,并決定是否讓設備啟動。在這里,兩個OnTemperatureChanged方法都是訂閱者方法。作為訂閱者方法很重要的是它們的參數(shù)和返回值類型必須和自動調溫器中的委托匹配。
?
二、定義發(fā)布者
?
Thermostat類負責向heater和cooler對象實例報告溫度的變化。
??? public class Thermostat
??? {
??????? public delegate void TemperarureChangeHandler(float newTemperature);
??????? public TemperarureChangeHandler OnTemperatureChange
??????? { get; set; }
??????? public float CurrentTemperature//接受當前的溫度
??????? { get; set; }
}
這個類的第一個成員是TemperarureChangeHandler 委托。定義了訂閱者的方法類型,就是說在Heater和Cooler類中的OnTemperatureChanged成員方法和TemperarureChangeHandler委托是匹配的。OnTemperatureChange成員屬性是TemperarureChangeHandler委托類型的,將會用來存儲著訂閱者列表。最后一個CurrentTemperature屬性是用來接收當前的溫度的。
?
三、連接發(fā)布者和訂閱者
??? public class Program
??? {
??????? Thermostat thermostat = new Thermostat();
??????? Heater heater = new Heater(60);
??????? Cooler cooler = new Cooler(80);
??????? string temerature;
??????? thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
??????? thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
??????? Console.Write("Enter temperature:");
??????? temerature = Console.ReadLine();
??????? thermostat.CurrentTemperature = int.Parse(temerature);
}
注意上述代碼使用+=運算符來直接賦值,向OnTemperatureChange委托注冊了兩個訂閱者。但是目前還沒有寫任何代碼將溫度變化發(fā)布給訂閱者。
?
四、調用委托,向訂閱者通知溫度的變化
public class Thermostat
{
…
…
??? public float CurrentTemperature//當CurrentTemperature屬性每次變化的時候調用委托來向訂閱者通知溫度的變化
??? {
??????? get
??????? {
??????????? return _CurrentTemperature;
??????? }
??????? set
??????? {
??????????? if (value != CurrentTemperature)
??????????? {
??????????????? _CurrentTemperature=value;
??????????????? OnTemperatureChange(value);
??????????? }
??????? }
??? }
private float _CurrentTemperature;
}
成功了成功了,現(xiàn)在對CurrentTemperature賦值包含了一些特殊的邏輯,可以向訂閱者通知CurrentTemperature發(fā)生了變化。為了向所有的訂閱者發(fā)出通知,只需執(zhí)行一個簡單的C# 語句即:OnTemperatureChange(value); 這個語句將溫度的變化發(fā)給Cooler和Heater的對象,只需執(zhí)行一個調用,即可向多個訂閱者發(fā)出通知。這里的實現(xiàn)就是基于一個委托變量可以保存一個委托鏈。
?
五、在調用委托之前必須檢查委托對象是否為空
public class Thermostat
{
…
…
??? public float CurrentTemperature//當CurrentTemperature屬性每次變化的時候調用委托檢查空值
??? {
??????? get
??????? {
??????????? return _CurrentTemperature;
??????? }
??????? set
??????? {
??????????? if (value != CurrentTemperature)
??????????? {
??????????????? _CurrentTemperature = value;
??????????????? TemperarureChangeHandler localOnChange = OnTemperatureChange;//OnTemperatureChange中發(fā)生的任何改變都會在localOnChange中反映出來
??????????????? if (localOnChange != null)
??????????????? {
??????????????????? localOnChange(value);
??????????????? }
?????? ?????}
??????? }
}
private float _CurrentTemperature;
}
在這里,并不是一開始就檢查空值,而是首先是將OnTemperatureChange賦值給 localOnChange因為OnTemperatureChange?的訂閱者被不是同一個線程的方法移除時候,那么不會觸發(fā)NullReferenceException異常。
?
六、處理來自訂閱者的異常
public class Thermostat
{
…
…
??????? public float CurrentTemperature//當CurrentTemperature屬性每次變化的時候調用委托并處理來自訂閱者的異常
?????? {
??????????? get
?????? ? {
??????????????? return _CurrentTemperature;
???????? }
??????????? set
???????? {
??????????????? if (value != CurrentTemperature)
??????????? {
??????????????????? _CurrentTemperature = value;
?
??????????????????? //OnTemperatureChange中發(fā)生的任何改變都會在localOnChange中反映出來??????????????????? TemperarureChangeHandler localOnChange = OnTemperatureChange;
??????????????????? if (localOnChange != null)
????????? ?????? {
?
????????????????? //防止因為訂閱者的異常導致在異常后面的訂閱者不能接收到發(fā)布事件??????????????????????? foreach (TemperarureChangeHandler handler in localOnChange.GetInvocationList())
????????????????? {
??????????????????????????? try
???????????????????? {
??????????????????????????????? handler(value);
???????????????????? }
??????????????????????????? catch (Exception exception)
???????????????????? {
??????????????????????????????? Console.WriteLine(exception.Message);
???????????????????? }
??????????????? ? }
?????????????? }
??????????? }
???????? }
???? }
??????? private float _CurrentTemperature;
}
?
委托鏈是將多個方法串聯(lián)在一起的,假如一個委托鏈上有A、B、C三個注冊的方法,但是假如A中出現(xiàn)異常,B和C是不會繼續(xù)執(zhí)行的,那么解決的辦法就是用foreach 上面的代碼就是解決方案。
?
七、事件的出現(xiàn)
講了這么多的還是沒有講到事件,哈哈,是不是被耍了啊。不要急,一會就會講到事件了啊。
我們看上面的委托處理還是很不錯的,不過有兩點不是很好
1、?在Main方法中給委托注冊事件
???? thermostat.OnTemperatureChange += heater.OnTemperatureChanged;
???? thermostat.OnTemperatureChange += cooler.OnTemperatureChanged;
假設我們不小心的把+=寫成了=,就會出現(xiàn)我們不想要的結果,就會在thermostat.OnTemperatureChange 中保存最后一個賦值的方法,而之前的都會被重寫,容易出錯。
2、?委托可以在包容類之外被執(zhí)行調用
?
還是在Main方法中可以加上這句話:
?
thermostat.OnTemperatureChange(56);
?
加上這句話會導致的結果是:即使thermostat的CurrentTemperature沒有發(fā)生變化,OnTemperatureChange也能被調用。因此thermostat訂閱者有可能被通知說溫度變化了,而實際上CurrentTemperature的溫度并沒有變化。
?
C# 用event 關鍵字解決上面的兩個問題。代碼:
?
??? public class Thermostat
??? {
??????? public class TemperatureArgs : System.EventArgs
??????? {
??????????? public TemperatureArgs(float newTemperature)
??????????? {
??????????????? NewTemperature = newTemperature;
??????????? }
??????????? public float NewTemperature
??????????? { get; set; }
??????? }
??????? //public delegate void TemperarureChangeHandler(float newTemperature);
??????? public delegate void TemperarureChangeHandler(object sender,TemperatureArgs newTemperature);
??????? public event TemperarureChangeHandler OnTemperatureChange = delegate { };
?
??????? public float CurrentTemperature//當CurrentTemperature屬性每次變化的時候調用委托并處理來自訂閱者的異常
??????? {
??????????? get
??????????? {
??????????????? return _CurrentTemperature;
??????????? }
??????????? set
??????????? {
??????????????? if (value != CurrentTemperature)
??????????????? {
??????????????????? _CurrentTemperature = value;
??????????????????? TemperarureChangeHandler localOnChange = OnTemperatureChange;//OnTemperatureChange中發(fā)生的任何改變都會在localOnChange中反映出來
??????????????????? if (localOnChange != null)
??????????????????? {
??????????????????????? foreach (TemperarureChangeHandler handler in localOnChange.GetInvocationList())//防止因為訂閱者的異常導致在異常后面的訂閱者不能接收到發(fā)布事件
??????????????????????? {
??????????????????????????? try
??????????????????????????? {
??????????????????????????????? handler(this,new TemperatureArgs(value));
????????????????????????? ??}
??????????????????????????? catch (Exception exception)
??????????????????????????? {
??????????????????????????????? Console.WriteLine(exception.Message);
??????????????????????????? }
??????????????????????? }
??????????????????? }
??????????????? }
??????????? }
??????? }
??????? private float _CurrentTemperature;
}
有了這樣的聲明,在加上event關鍵字后,提供了我們需要的全部封裝。首先會禁止使用賦值運算符,然后只有包容類才能調用向所有的訂閱者發(fā)出通知的委托。
總結
- 上一篇: 答TOGAF企业架构的一些问题
- 下一篇: “2011年度IT博客大赛”支持fang