明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

C#里的委托與事件完成Observer

[摘要]一、委托的簡(jiǎn)介1、委托的聲明:<access modifier> delegate <returnType> HandlerName ([parameters])例如:public delegate void PrintHandler(string str); ...
一、委托的簡(jiǎn)介

1、委托的聲明:

<access modifier> delegate <returnType> HandlerName ([parameters])

例如:

public delegate void PrintHandler(string str);



      委托聲明定義了一種類(lèi)型,它用一組特定的參數(shù)以及返回類(lèi)型來(lái)封裝方法。對(duì)于靜態(tài)方法,委托對(duì)象封裝要調(diào)用的方法。對(duì)于實(shí)例方法,委托對(duì)象同時(shí)封裝一個(gè)實(shí)例和該實(shí)例上的一個(gè)方法。如果您有一個(gè)委托對(duì)象和一組適當(dāng)?shù)膮?shù),則可以用這些參數(shù)調(diào)用該委托。



2、委托的使用:

using System;



public class MyClass

{

                public static void Main()

                {

                                PrintStr myPrinter = new PrintStr();

                                PrintHandler myHandler = null;

                                myHandler += new PrintHandler(myPrinter.CallPrint); // 將委托鏈接到方法,來(lái)實(shí)例化委托

                                if(myHandler!=null)

                                                myHandler("Hello World!");    // 調(diào)用委托,相當(dāng)于匿名調(diào)用委托所鏈接的方法

                                Console.Read();

                }

}



public delegate void PrintHandler(string str);    // 聲明委托類(lèi)型



public class PrintStr

{               

                public void CallPrint(string input)

                {

                                Console.WriteLine(input);

                }

}






在C#中使用委托方法:

·          創(chuàng)建委托所使用的方法必須和委托聲明相一致(參數(shù)列表、返回值都一致)

·          利用 +=、-=來(lái)進(jìn)行委托的鏈接、取消鏈接或直接使用Delegate.Combine和Delegate.Remove方法來(lái)實(shí)現(xiàn)

·          可以使用MulticastDelegate的實(shí)例方法GetInvocationList()來(lái)獲取委托鏈中所有的委托

·          不能撰寫(xiě)包含 out 參數(shù)的委托



二、事件的簡(jiǎn)介

C# 中的“事件”是當(dāng)對(duì)象發(fā)生某些事情時(shí),類(lèi)向該類(lèi)的客戶(hù)提供通知的一種方法。

1、事件的聲明:

聲明的格式為:<access modifier> event <delegate type> EventName



        因?yàn)槭褂梦衼?lái)聲明事件,所以在類(lèi)里聲明事件時(shí),首先必須先聲明該事件的委托類(lèi)型<delegate type>(如果尚未聲明的話)。在上面我們已經(jīng)提到過(guò)了委托類(lèi)型的聲明,但是在.net  framework下為事件使用的委托類(lèi)型進(jìn)行聲明時(shí)有更嚴(yán)格的規(guī)定:

(1)、 事件的委托類(lèi)型應(yīng)采用兩個(gè)參數(shù);

(2)、兩個(gè)參數(shù)分別是:指示事件源的“對(duì)象源”參數(shù)和封裝事件的其他任何相關(guān)信息的“e”參數(shù);

(3)、“e”參數(shù)的類(lèi)型應(yīng)為EventArgs 類(lèi)或派生自 EventArgs 類(lèi)。

如下的定義:

public delegate void PrintHandler(object sender,System.EventArgs e);



然后我們才能聲明該委托類(lèi)型的事件

例如:

public event PrintHandler Print;

當(dāng)事件發(fā)生時(shí),將調(diào)用其客戶(hù)提供給它的委托。



2、調(diào)用事件:

        類(lèi)聲明了事件以后,可以就像處理所指示的委托類(lèi)型的字段那樣處理該事件。如果沒(méi)有任何客戶(hù)將委托與該事件綁定,則該字段將為空;否則該字段引用應(yīng)在調(diào)用該事件時(shí)調(diào)用的委托。因此,調(diào)用事件時(shí)通常先檢查是否為空,然后再調(diào)用事件。(調(diào)用事件,即觸發(fā)事件,只能從聲明該事件的類(lèi)內(nèi)進(jìn)行)



if(Print != null)

{

                Print (this,e);

}



3、事件綁定:

        從類(lèi)的外面來(lái)看,事件就象類(lèi)的一個(gè)公共成員,通過(guò) 類(lèi)名.事件名 的形式來(lái)訪問(wèn),但是只能對(duì)它做綁定和解除綁定的操作,而不能有其他操作。



類(lèi)名. Print += new PrintHandler(綁定的方法名)  // 將某個(gè)方法綁定到Print事件上

類(lèi)名. Print  -= new PrintHandler(綁定的方法名)  // 將某個(gè)已綁定到Print事件上的方法從Print事件上解除



三、委托和事件的使用

委托和事件在用戶(hù)界面程序里用的比較的多,比如象在winform或webform的用戶(hù)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()方法

{

                ……  

}



然而除了用戶(hù)界面程序外,在很多其他地方也用到了事件驅(qū)動(dòng)模式,比如觀察者模式(Observer)或發(fā)布/訂閱(Publish/Subscribe)里:在一個(gè)類(lèi)里發(fā)布(Publish)某個(gè)可以被觸發(fā)的事件,而其他的類(lèi)就可以來(lái)訂閱(Subscribe)該事件。一旦這個(gè)發(fā)布者類(lèi)觸發(fā)了該事件,那么運(yùn)行時(shí)環(huán)境會(huì)立刻告知所有訂閱了該事件的訂閱者類(lèi):這個(gè)事件發(fā)生了!從而各個(gè)訂閱者類(lèi)可以作出它們自己的反應(yīng)(調(diào)用相應(yīng)方法)。



我們來(lái)舉一個(gè)生活中的實(shí)際例子來(lái)說(shuō)明如何使用委托和事件,以及使用委托和事件所帶來(lái)的好處:



比如說(shuō)有一個(gè)公司(場(chǎng)景),你是老板,手下有主管和員工,作為老板你會(huì)指派(委托)主管管理員工的工作,如果某個(gè)員工玩游戲,則讓某個(gè)主管從該員工的薪水里扣去500元錢(qián)。

這就是現(xiàn)實(shí)中的委托。

而在寫(xiě)程序中,假設(shè)程序員就是老板,有兩個(gè)類(lèi)分別為主管和員工,而主管小王和員工小張就是兩個(gè)類(lèi)的對(duì)象實(shí)例。員工類(lèi)有一個(gè)方法:玩游戲,同時(shí)就有一個(gè)玩游戲的事件,他一玩游戲就會(huì)激發(fā)這個(gè)事件。而主管類(lèi)就是負(fù)責(zé)處理該事件的,他負(fù)責(zé)把玩游戲的員工的薪水扣除500。



(一)、首先,我們來(lái)看看在非委托的情況下比較常見(jiàn)的一種設(shè)計(jì)方式(當(dāng)然這不是唯一的方式,也不是最好的方式,但是很常見(jiàn)):



using System;

namespace CSharpConsole

{

                public class 場(chǎng)景

                {

                                [STAThread]

                                public static void Main(string[] args)

                                {

                                                Console.WriteLine("場(chǎng)景開(kāi)始了.");

                                                // 生成主管類(lèi)的對(duì)象實(shí)例 小王

                                                主管 小王 = new 主管();

                                                // 生成員工類(lèi)的對(duì)象實(shí)例 小張,指定他的主管

                                                員工 小張 = new 員工(小王);

                                

                                                Console.WriteLine("該員工本有的薪水:" + 小張.薪水.ToString());

            

                                                // 員工開(kāi)始玩游戲

                                                小張.玩游戲();



                                                Console.WriteLine("現(xiàn)在該員工還剩下:" +小張.薪水.ToString());

                                

                                                Console.WriteLine("場(chǎng)景結(jié)束");

                                                Console.ReadLine();

                                }

                }







                // 負(fù)責(zé)扣錢(qián)的人----主管

                public class 主管

                {

                                public 主管()

                                {

                                                Console.WriteLine("生成主管");

                                }



                                public void 扣薪水(員工 employee)

                                {

                                                Console.WriteLine("主管:好小子,上班時(shí)間膽敢玩游戲");

                                                Console.WriteLine("主管:看看你小子有多少薪水");

                                

                                                Console.WriteLine("開(kāi)始扣薪水...");

                                                System.Threading.Thread.Sleep(1000);



                                                employee.薪水 = employee.薪水 - 500;



                                                Console.WriteLine("扣薪水執(zhí)行完畢.");           

                                }

                }



                // 如果玩游戲,則會(huì)引發(fā)事件

                public class 員工

                {

                                // 保存員工的薪水

                                private int m_Money;

                                // 保存該員工的主管

                                private 主管 m_Manager;



                                public 員工(主管 manager)

                                {

                                                Console.WriteLine("生成員工.");

                                                m_Manager = manager;  // 通過(guò)構(gòu)造函數(shù),初始化員工的主管。

                                                m_Money = 1000; // 通過(guò)構(gòu)造函數(shù),初始化員工的薪水。

                                }



                                public int 薪水 // 此屬性可以操作員工的薪水 。

                                {

                                                get

                                                {

                                                                return m_Money;

                                                }

                                                set

                                                {

                                                                m_Money = value;

                                                }

                                }



                                public void 玩游戲()

                                {

                                                Console.WriteLine("員工開(kāi)始玩游戲了..");

                                                Console.WriteLine("員工:CS真好玩,哈哈哈! 我玩...");

                                                System.Threading.Thread.Sleep(1000);

                                                

                                                m_Manager. 扣薪水(this);

                                }

                }

}



這種方法所帶來(lái)的問(wèn)題: 員工類(lèi)和主管類(lèi)的耦合性太高

1、   在客戶(hù)程序里必須先創(chuàng)建了主管類(lèi)之后才能生成員工類(lèi),如果在不需要主管類(lèi)對(duì)象而只需員工類(lèi)對(duì)象的地方,為了創(chuàng)建所需的員工類(lèi)對(duì)象實(shí)例,你也不得不去先創(chuàng)建一個(gè)主管類(lèi)的對(duì)象實(shí)例;

2、   如果場(chǎng)景劇本(即客戶(hù)程序需求)發(fā)生了變化

(1)、現(xiàn)在要讓一個(gè)新的角色(一個(gè)新的類(lèi)),如保安,來(lái)代替主管,負(fù)責(zé)在員工玩游戲時(shí)扣員工薪水,那么我們不得不去修改員工類(lèi),或許還需要修改主管類(lèi);

(2)、如果場(chǎng)景劇本增加新的需求,要求員工在玩游戲后,不但要扣薪水,還要在績(jī)效上扣分,那么我們也不得不修改員工類(lèi)。


(二)、利用委托的實(shí)現(xiàn):



下面有個(gè)例子:在C# 控制臺(tái)應(yīng)用程序編輯運(yùn)行成功:



using System;

namespace CSharpConsole

{

                // 定義委托

                public delegate void PlayGameHandler(object sender,System.EventArgs e);



                // 負(fù)責(zé)扣錢(qián)的人----主管

                public class 主管

                {

                                public 主管()

                                {

                                                Console.WriteLine("生成主管");

                                }



                                public void 扣薪水(object sender,EventArgs e)

                                {

                                                Console.WriteLine("主管:好小子,上班時(shí)間膽敢玩游戲");

                                                Console.WriteLine("主管:看看你小子有多少薪水");

                

                                                員工 employee = (員工)sender;

                

                                                Console.WriteLine("開(kāi)始扣薪水...");

                                                System.Threading.Thread.Sleep(1000);

                                                employee.薪水 = employee.薪水 - 500;

                                                Console.WriteLine("扣薪水執(zhí)行完畢.");           

                                }

                }



                // 如果玩游戲,則會(huì)引發(fā)事件

                public class 員工

                {

                                // 先定義一個(gè)事件,這個(gè)事件表示員工在玩游戲。

                                public event PlayGameHandler PlayGame;

                                // 保存員工薪水的變量

                                private int m_Money;



                                public 員工()

                                {

                                                Console.WriteLine("生成員工.");

                                                m_Money = 1000; // 構(gòu)造函數(shù),初始化員工的薪水。

                                }



                                public int 薪水 // 此屬性可以操作員工的薪水 。

                                {

                                                get

                                                {

                                                                return m_Money;

                                                }

                                                set

                                                {

                                                                m_Money = value;

                                                }

                                }



                                public void 玩游戲()

                                {

                                                Console.WriteLine("員工開(kāi)始玩游戲了..");

                                                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 場(chǎng)景

                {

                                [STAThread]

                                public static void Main(string[] args)

                                {

                                                Console.WriteLine("場(chǎng)景開(kāi)始了.");

                                                // 生成主管類(lèi)的對(duì)象實(shí)例 小王

                                                主管 小王 = new 主管();

                                                // 生成員工類(lèi)的對(duì)象實(shí)例 小張

                                                員工 小張 = new 員工();



                                                // 設(shè)下委托,指定監(jiān)視

                                                小張.PlayGame += new PlayGameHandler(小王. 扣薪水);

                                

                                                Console.WriteLine("該員工本有的薪水:" + 小張.薪水.ToString());

            

                                                // 員工開(kāi)始玩游戲

                                                小張.玩游戲();



                                                Console.WriteLine("現(xiàn)在該員工還剩下:" +小張.薪水.ToString());

                                

                                                Console.WriteLine("場(chǎng)景結(jié)束");

                                                Console.ReadLine();

                                }

                }

}




對(duì)于前面提出的問(wèn)題:

1、   解耦了主管類(lèi)和員工類(lèi)之間的必然聯(lián)系,可以單獨(dú)創(chuàng)建員工類(lèi)對(duì)象實(shí)例,而不用管是否有主管類(lèi)對(duì)象實(shí)例的存在;

2、   在客戶(hù)程序需求變化時(shí):

(1)、我們只需修改客戶(hù)程序,即上面例子里的class 場(chǎng)景,將委托改為如下:



保安 小李 = new 保安();

小張.PlayGame += new PlayGameHandler(小李. 扣薪水);



即可實(shí)現(xiàn)由保安來(lái)負(fù)責(zé)扣薪水的需求變化,而不用動(dòng)員工類(lèi)。

(2)、我們只需修改客戶(hù)程序,即上面例子里的class 場(chǎng)景,添加一個(gè)如下的委托:



                小張.PlayGame += new PlayGameHandler(某某. 扣績(jī)效分);



這個(gè)“某某”可以是主管,也可以是其他新的角色(新的類(lèi)),只需要在“某某”對(duì)應(yīng)的類(lèi)里定義扣績(jī)效分的動(dòng)作即可,而不用動(dòng)員工類(lèi)。



四、總結(jié):

      當(dāng)然,不使用委托和事件我們?nèi)匀豢梢栽O(shè)計(jì)出解耦的類(lèi),然而卻會(huì)增加很多的類(lèi)、接口以及關(guān)聯(lián)等等,增加了代碼量和程序的邏輯復(fù)雜性,而在.net里利用委托和事件我們只需少的多的代碼來(lái)實(shí)現(xiàn)。



  委托和事件的使用有如下幾個(gè)要素:


1、激發(fā)事件的對(duì)象-----就是員工小張
2、處理對(duì)象事件的對(duì)象-----就是主管小王
3、定義委托,就是你讓主管小王監(jiān)視員工小張。

如果這三個(gè)要素都滿(mǎn)足的話,則你就寫(xiě)出了一個(gè)完整事件的處理。