C#裡的委託和事件實現Observer(觀察者)
1、委託的宣告:
delegate HandlerName ([parameters])
例如:
public delegate void PrintHandler(string str);
委託宣告定義了一種型別,它用一組特定的引數以及返回型別來封裝方法。對於靜態方法,委託物件封裝要呼叫的方法。對於例項方法,委託物件同時封裝一個例項和該例項上的一個方法。如果您有一個委託物件和一組適當的引數,則可以用這些引數呼叫該委託。
2、委託的使用:
using System;
{
public static void Main()
{
PrintStr myPrinter = new PrintStr();
PrintHandler myHandler = null;
myHandler
if(myHandler!=null) {
myHandler("Hello World!"); // 呼叫委託,相當於匿名呼叫委託所連結的方法
Console.Read();
}
}
public class PrintStr
{
public void CallPrint(string input)
{
Console.WriteLine(input);
}
}
在C#中使用委託方法:
· 建立委託所使用的方法必須和委託宣告相一致(引數列表、返回值都一致)
· 利用 +=、-=來進行委託的連結、取消連結或直接使用Delegate.Combine和Delegate.Remove方法來實現
· 可以使用MulticastDelegate的例項方法GetInvocationList()來獲取委託鏈中所有的委託
· 不能撰寫包含 out 引數的委託
二、事件的簡介
C# 中的"事件"是當物件發生某些事情時,類向該類的客戶提供通知的一種方法。
1、事件的宣告:
宣告的格式為:
event EventName
因為使用委託來宣告事件,所以在類裡宣告事件時,首先必須先宣告該事件的委託型別(如果尚未宣告的話)。在上面我們已經提到過了委託型別的宣告,但是在.net framework下為事件使用的委託型別進行宣告時有更嚴格的規定:
(1)、 事件的委託型別應採用兩個引數;
(2)、兩個引數分別是:指示事件源的"物件源"引數和封裝事件的其他任何相關資訊的"e"引數;
(3)、"e"引數的型別應為EventArgs 類或派生自 EventArgs 類。
上面的說法好像有誤!!!“事件的委託型別應採用兩個引數”???
如下的定義:
然後我們才能宣告該委託型別的事件
例如:
public event PrintHandler Print;
注意:此時的Print即是 事件(event),又是 委託(PrintHandler)
當事件發生時,將呼叫其客戶提供給它的委託。
2、呼叫事件:
類聲明瞭事件以後,可以就像處理所指示的委託型別的欄位那樣處理該事件。如果沒有任何客戶將委託與該事件繫結,則該欄位將為空;否則該欄位引用應在呼叫該事件時呼叫的委託。因此,呼叫事件時通常先檢查是否為空,然後再呼叫事件。(呼叫事件,即觸發事件,只能從宣告該事件的類內進行)
從類的外面來看,事件就象類的一個公共成員,通過 類名.事件名 的形式來訪問,但是隻能對它做繫結和解除繫結的操作,而不能有其他操作。
類名. Print += new PrintHandler(繫結的方法名) // 將某個方法繫結到Print事件上
類名. Print -= new PrintHandler(繫結的方法名) // 將某個已繫結到Print事件上的方法從Print事件上解除 三、委託和事件的使用
委託和事件在使用者介面程式裡用的比較的多,比如象在winform或webform的使用者UI上的button和它的click事件:
將Button1_Click()方法繫結到按鈕控制元件Button1的Click事件上
this .Button1.Click += new System.EventHandler( this . Button1_Click);
private void Button1_Click( object sender, System.EventArgs e) // Button1_Click()方法
{
} 然而除了 使用者介面程式 外,在很多其他地方也用到了事件驅動模式,比如 觀察者模式(Observer) 或 釋出/訂閱(Publish/Subscribe) 裡:在一個類裡釋出(Publish)某個可以被觸發的事件,而其他的類就可以來訂閱(Subscribe)該事件。一旦這個釋出者類觸發了該事件,那麼執行時環境會立刻告知所有訂閱了該事件的訂閱者類:這個事件發生了!從而各個訂閱者類可以作出它們自己的反應(呼叫相應方法)。
我們來舉一個生活中的實際例子來說明如何使用委託和事件,以及使用委託和事件所帶來的好處:
比如說有一個公司(場景),你是老闆,手下有主管和員工,作為老闆你會指派(委託)主管管理員工的工作,如果某個員工玩遊戲,則讓某個主管從該員工的薪水裡扣去500元錢。
這就是現實中的委託。
而在寫程式中, 假設程式設計師就是老闆,有兩個類分別為主管和員工,而主管小王和員工小張就是兩個類的物件例項。員工類有一個方法:玩遊戲,同時就有一個玩遊戲的事件,他一玩遊戲就會激發這個事件。而主管類就是負責處理該事件的,他負責把玩遊戲的員工的薪水扣除500。
(一)、首先,我們來看看在非委託的情況下比較常見的一種設計方式(當然這不是唯一的方式,也不是最好的方式,但是很常見):
這種方法所帶來的問題: 員工類和主管類的耦合性太高
1、 在客戶程式裡必須先建立了主管類之後才能生成員工類,如果在不需要主管類物件而只需員工類物件的地方,為了建立所需的員工類物件例項,你也不得不去先建立一個主管類的物件例項;
2、 如果場景劇本(即客戶程式需求)發生了變化
(1)、現在要讓一個新的角色(一個新的類),如保安,來代替主管,負責在員工玩遊戲時扣員工薪水,那麼我們不得不去修改員工類,或許還需要修改主管類;
(2)、如果場景劇本增加新的需求,要求員工在玩遊戲後,不但要扣薪水,還要在績效上扣分,那麼我們也不得不修改員工類。
(二)、利用委託的實現:
using System;
namespace CSharpConsole
{
public class 場景
{
[STAThread]
public static void Main( string [] args)
{
Console.WriteLine( " 場景開始了. " );
// 生成主管類的物件例項 小王
主管 小王 = new 主管();
// 生成員工類的物件例項 小張,指定他的主管
員工 小張 = new 員工(小王);
Console.WriteLine( " 該員工本有的薪水: " + 小張.薪水.ToString());
// 員工開始玩遊戲
小張.玩遊戲();
Console.WriteLine( " 現在該員工還剩下: " + 小張.薪水.ToString());
Console.WriteLine( " 場景結束 " );
Console.ReadLine();
}
}
// 負責扣錢的人----主管
public class 主管
{
public 主管()
{
Console.WriteLine("生成主管");
}
public void 扣薪水(員工 employee)
{
Console.WriteLine("主管:好小子,上班時間膽敢玩遊戲");
Console.WriteLine("主管:看看你小子有多少薪水");
Console.WriteLine("開始扣薪水");
System.Threading.Thread.Sleep(1000);
employee.薪水 = employee.薪水 - 500;
Console.WriteLine("扣薪水執行完畢.");
}
}
// 如果玩遊戲,則會引發事件
public class 員工
{
// 儲存員工的薪水
private int m_Money;
// 儲存該員工的主管
private 主管 m_Manager;
public 員工(主管 manager)
{
Console.WriteLine("生成員工.");
m_Manager = manager;// 通過建構函式,初始化員工的主管。
m_Money = 1000;// 通過建構函式,初始化員工的薪水。
}
public int 薪水 // 此屬性可以操作員工的薪水 。
{
get
{
return m_Money;
}
set
{
m_Money = value;
}
}
public void 玩遊戲()
{
Console.WriteLine("員工開始玩遊戲了..");
Console.WriteLine("員工:CS真好玩,哈哈哈! 我玩");
System.Threading.Thread.Sleep(1000);
m_Manager.扣薪水(this);
}
}
}
下面有個例子:在C# 控制檯應用程式編輯執行成功:
using System;
namespace CSharpConsole
{
// 定義委託
public delegate void PlayGameHandler(object sender, System.EventArgs e);
// 負責扣錢的人----主管
public class 主管
{
public 主管()
{
Console.WriteLine("生成主管");
}
public void 扣薪水(object sender, EventArgs e)
{
Console.WriteLine("主管:好小子,上班時間膽敢玩遊戲");
Console.WriteLine("主管:看看你小子有多少薪水");
員工 employee = (員工)sender;
Console.WriteLine("開始扣薪水");
System.Threading.Thread.Sleep(1000);
employee.薪水 = employee.薪水 - 500;
Console.WriteLine("扣薪水執行完畢.");
}
}
// 如果玩遊戲,則會引發事件
public class 員工
{
// 先定義一個事件,這個事件表示員工在玩遊戲。
public event PlayGameHandler PlayGame;
// 儲存員工薪水的變數
private int m_Money;
public 員工()
{
Console.WriteLine("生成員工.");
m_Money = 1000; // 建構函式,初始化員工的薪水。
}
public int 薪水 // 此屬性可以操作員工的薪水 。
{
get
{
return m_Money;
}
set
{
m_Money = value;
}
}
public void 玩遊戲()
{
Console.WriteLine("員工開始玩遊戲了..");
Console.WriteLine("員工:CS真好玩,哈哈哈! 我玩");
System.Threading.Thread.Sleep(1000);
System.EventArgs e = new EventArgs();
OnPlayGame(e);
}
protected virtual void OnPlayGame(EventArgs e)
{
if (PlayGame != null)
{
PlayGame(this, e);
}
}
}
public class 場景
{
[STAThread]
public static void Main(string[] args)
{
Console.WriteLine("場景開始了.");
// 生成主管類的物件例項 小王
主管 小王 = new 主管();
// 生成員工類的物件例項 小張
員工 小張 = new 員工();
// 設下委託,指定監視
小張.PlayGame += new PlayGameHandler(小王.扣薪水);
Console.WriteLine("該員工本有的薪水:" + 小張.薪水.ToString());
// 員工開始玩遊戲
小張.玩遊戲();
Console.WriteLine("現在該員工還剩下:" + 小張.薪水.ToString());
Console.WriteLine("場景結束");
Console.ReadLine();
}
}
}
1、 解耦了主管類和員工類之間的必然聯絡,可以單獨建立員工類物件例項,而不用管是否有主管類物件例項的存在;
2、 在客戶程式需求變化時:
(1)、我們只需修改客戶程式,即上面例子裡的class 場景,將委託改為如下:
保安 小李 = new 保安();
小張.PlayGame += new PlayGameHandler(小李. 扣薪水); 即可實現由保安來負責扣薪水的需求變化,而不用動員工類。
(2)、我們只需修改客戶程式,即上面例子裡的class 場景,新增一個如下的委託:
這個"某某"可以是主管,也可以是其他新的角色(新的類),只需要在"某某"對應的類裡定義扣績效分的動作即可,而不用動員工類。
四、總結:
當然,不使用委託和事件我們仍然可以設計出解耦的類,然而卻會增加很多的類、介面以及關聯等等,增加了程式碼量和程式的邏輯複雜性,而在.net裡利用委託和事件我們只需少的多的程式碼來實現。
委託和事件的使用有如下幾個要素:
1、激發事件的物件-----就是員工小張
2、處理物件事件的物件-----就是主管小王
3、定義委託,就是你讓主管小王監視員工小張。
如果這三個要素都滿足的話,則你就寫出了一個完整事件的處理。 分類: 03~c#