C++轉向C#的疑惑:C#中類間通信方法初探

來源:zhuweisky的專欄http://blog.csdn.net/zhuweisky/archive/2005/07/06/415756.aspx) 

C#中沒有自由函數也沒有自由變量,所有的一切都必須封裝在類中。在C++中,通過全局變量來進行函數間通信和類間通信是常用的方法,然而這在C#中卻變成不可能任務,這的確讓C++程序員苦惱。C#當然有它充分的理由不支持自由函數和自由變量,因爲“自由”就暗示着衝突、不安全等等影響程序健壯性和可維護性的因素。當然有很多技術可以解決C#中的類間通信問題,本文就介紹其中的幾種。

 

一.問題域涉及的主要Class

 

在介紹之前先作一下說明。一般我們可以把類間通信時需要的各相關數據元素封裝成一個結構,我稱之爲“通信結構”,將其記爲Information struct ,它是一個純粹的C結構,即除了公共的數據成員外,什麼都沒有。當類間需要交換消息時就可以生成一個“通信結構”實例,然後在類間傳遞。另外我把提供信息的類稱爲Provider Class (以下簡稱爲Provider),接受信息的類稱爲Master Class(以下簡稱爲Master)。當然,兩個相互通信的類可以互爲ProviderMaster。請注意,後面將出現Provider objectMaster object,它們對應於是ProviderMaster的實例。

       MasterProvider獲取所需信息主要有兩種方式:正向獲取和反向獲取。每一種方式又有幾種不同的實現方式。下面我將一一介紹。

 

二.正向獲取信息

       所謂“正向“獲取信息,就是按照通常的直線思考方式來考慮,Master需要從Provider獲取信息,就直接讓Master訪問Provider的某個成員。在這種方式下,Master處於主動地位,因爲總是由它去主動拜訪Provider,而Provider相對比較被動。

不出你所料,主要有以下幾種方法。

1.  Provider暴露一個成員變量或屬性

Master 可以通過訪問這個成員變量或屬性得到所需數據。這種方法對於得到一些簡單的非保密性信息是比較方便的,通常這些信息可以被Provider自己直接提供,並且不易發生變化,比如獲取某個控件(當然是一個Provider object)的sizecolor等等。此方法的不利之處是,如果Provider所提供的數據是保密性的,那麼這個安全性就得不到保證。

 

2.  Provider暴露一個方法

如果所需的信息比較複雜(比如需要通過簡單的計算才能得到),或經常處於變化之中,通過一個方法來提供這樣的信息無疑是更好的選擇。有兩種實現方式:

1)通過返回值

這個我們經常用,在這種情況下,Master還可以通過參數來和Provider進行更多的交流。如:

public Information GetInformation( 參數 ) ;

2)通過refout參數

     如果一個方法的返回值被用於其它目的,如判斷方法是否執行成功等,此時可以用refout來將所需的信息從方法中“帶出”。如:

public int GetInformation(out Information info) ;

 

或許你已經看出來了,正向獲取信息的方式的主要問題在於,Master不知道Provider會於何時準備好所需的數據,也就是說,Master是在一個合適的時間拜訪Provider嗎?如果時機不恰當,顯然就得不到正確的信息。

爲了解決這個問題,我們可以採取反向獲取信息的方式。

 

三.反向獲取信息

所謂“反向”獲取信息,就是Provider在準備好數據後,主動將數據提供給Master。與正向獲取信息的方式相比較,可以看到,主――客關係發生了變化,現在是Provider主動去拜訪Master。這就很自然地解決了正向獲取信息方式的時機可能不成熟的問題。

在實際中,用的最多的就是反向獲取信息的方式,普遍的情況是這樣的:在Master需要數據時,創建一個Provider objectProvider object取得數據並作相關處理後將所需信息保存在一個Information object中,然後將該Information object提交給Master,自此該Provider object生命期結束,接着Master就可以處理得到的數據了。爲了使讀者有一個直觀的認識,舉個簡陋的例子:

   

       1  Master object                       2  Provider object

 

這是一個簡單圖書信息管理系統,圖1Master object界面,圖2Provider object界面,當點擊圖1的“添加”按鈕時就會彈出圖2所示界面,當用戶填寫完信息後點擊“提交按鈕”,即可將一個新的圖書信息添加到管理系統中。

此時,Information結構可如下設計:

public struct Information

    {

       public string book_name ;

       public string author ;

       public string date ;

}

 

1.通過類的靜態成員變量數據通信

 

這是最簡單的直接模擬C++中的利用全局變量進行通信的方法。我們把一個Information對象作爲Master的靜態成員變量,並將其修飾符設爲public ,那麼Provider object就可以對其進行寫操作,當Provider寫操作完成之後,Master就可以處理靜態的Information對象了。現在,問題出現了――Master怎麼知道Provider完成了寫操作呢?回調函數!!!――這是C++程序員的第一反應,在C#中可以嗎?可以,只不過C#提供的是一種安全的函數指針,叫委託。我們前面已經指出Provider對象通常都是由Master對象創建的,那麼Master對象在創建Provider對象時可以傳給Provider對象一個委託實例,當Provider完成寫操作之後就可以通過此委託實例完成回調了。這個過程可如圖3所示。

             3  通過類的靜態成員變量進行類間通信

 

下面看看這種方法的主要C#僞碼。

首先要定義委託:

public delegate void D_Callback() ;

 

再看Master類:

class Master

    {  

       public static Information object_I ;     

       public void GetInformation()

       {

            D_Callback callback = new D_Callback(ProcessInformation) ;

           Provider object_P = new Provider(callback) ;

           ......//如,顯示object_P界面

       }

       private void ProcessInformation()

       {

           ......//處理object_I

       }

        ......

}  

 

接下來看看Provider如何實現:

class Provider

    {

        private D_Callback back ;

        public Provider(D_Callback yourback)  //構造函數需以“回調函數”作爲參數

        {

            this.back = yourback ;

        }

        //如當用戶點擊圖2中的“提交”按鈕時,將調用以下處理函數

        private void button1_Click()

        {

            ......  //訪問Master.object_I ,並將信息寫入

            back() ;//觸發回調函數調用,即調用Master中的ProcessInformation函數

            this.close() ;// Provider對象完成任務,可以被GC回收了。

        }

}

 

很簡單,不是嗎?是的,足夠簡單,它只不過是C++中用全局變量進行通信的C#版本。然而這種方法好嗎?不好,這種方法僅僅是完成了任務,但是幹得並不漂亮,甚至,這種實現是非常醜陋的。原因至少有兩點:

首先,使Information對象成爲Master的public靜態對象就是非常不合適的,因爲如果這樣,不僅是Provider類而且所有的其它的類(包括含有惡意的類)都可以向這個靜態區域寫入信息,這就喪失了安全性。

其次,Master要將自己的成員函數(作爲回調的函數)包裝成委託傳遞給Provider類作爲構造參數,這就導致了Master類和Provider類非常緊密的耦合,如同聯體嬰兒一樣。如果我們想僅僅複用其中的一個類而不牽帶另一個類是完全不可能的,這違背了OOD的基本原則。

很糟糕,不是嗎?!讓我們來看一個改良版本。

 

2.通過堆棧對象複製進行數據通信

 

Provider對象中將獲取數據存放到自己的私有Information對象中,然後用該Information對象調用從構造函數傳遞進來的委託,這樣跳轉到Master的回調函數時,因爲Informationstruct,而struct是值類型,所以將會在棧上覆制這個Information型對象,其副本就可以被回調函數訪問了。如此,信息就從Povider傳遞到Master了。

    此過程可表示爲圖4所示的步驟。

 

4  通過類的靜態成員變量進行類間通信

 

再來看看代碼有何改變:

public delegate void D_Callback(Information object_I) ;

//此處要將Information對象作爲回調函數的參數

class Master

    {  

       //不再擁有靜態的Information成員

       public void GetInformation()

       {

            D_Callback callback = new D_Callback(ProcessInformation) ;

           Provider object_P = new Provider(callback) ;

           ......

       }

       private void ProcessInformation(Information object_I)      

{                                                    

           ......//處理object_I副本

       }

        ......

}  

 

接下來看看Provider的改變:

class Provider

    {

       private D_Callback back ;

 

        //增加私有Information成員用以存放要傳遞的信息

private Information object_I ;

 

       public Provider(D_Callback yourback)

       {

           this.back = yourback ;

       }

 

       private void button1_Click()

       {

           ......  //將信息寫入this.object_I

            //object_I作爲參數調用Master中的ProcessInformation函數

           back(this.object_I) ;

this.close() ;

}

}

這種方法通過堆棧對象複製而避免暴露一塊公共的內存區域(public static member),

增強了安全性,但是MasterProvider之間的緊耦合性並沒有減弱。難道沒有完美的解決方案嗎?有!那就是通過事件的發佈――預定方式,採用這種方式,發佈事件的類根本就不必知道預定事件的類的任何情況,從而將以前的MasterProvider之間的循環依賴轉化爲單向依賴(僅僅Master依賴於Provider),這就有效地降低了MasterProvider之間的耦合。

 

3.通過事件機制進行通信

事件機制爲類間通信提供了一種優雅、高效、靈活的解決方案。

我們只需在Provider類中聲明併發布事件,並在適當的時候觸發事件;然後在Master類中預定事件,並定義事件處理函數就可以了。此方式下, Provider類不僅能爲Master類所用,而且也能爲其它需要得到同樣信息的類使用。如此看來,Master類只是Provider類的一個客戶(client)而已。整個通信過程可描述爲圖5所示的步驟。

             5   通過事件機制進行通信

 

看看是如何實現的。

首先一個委託的實例如果要成爲事件,委託的原型聲明必須是如下形式:

public delegate void D_Callback (object sender ,MyEventArgs e) ;

注意,MyEventArgs必須從EventArgs類繼承,它是用來包裝要傳遞的信息的,這裏我們可以簡單的如下實現:

public class MyEventArgs:System.EventArgs

    {

       public Information object_I;

}

接着看看Provider是如何發佈事件並觸發事件的:

class Provider

    {

       public event D_Callback  PutOutInformation;  //發佈事件

                                                               

       private void button1_Click()

       {

            MyEventArgs  args = new MyEventArgs() ;

            ......  //將信息寫入args.object_I       

           PutOutInformationthisargs); //觸發事件

this.close() ;

}

}

是不是簡單、清晰、明瞭?!Provider根本就不需要知道Master類的存在。

再來看看Master類如何預定事件,以及事件發生時如何獲得所需信息。

class Master

    {  

       public void GetInformation()

       {

             Privoder object_P = new Privoder() ;

              //預定事件 

              object_P. PutOutInformation

                        += new D_Callback(this.ProcessInformation) ;

              .....

       }

       

       private void ProcessInformation(object sender ,MyEventArgs e)     

{    

         .....//處理e.object_I                                          

        }

        ......

    }  

上面的代碼簡單明瞭,就不用我再羅嗦了。

 

C#是一種完全面向對象的語言,相對於C++,它剔除了全局變量和全局函數,並且引入了事件、屬性等面向對象編程的常用特性。由於沒有了全局變量,所以在C#中必須尋找另外的方案來解決類間通信的問題,而C#提供的事件機制恰好是解決這類問題的一劑良方,雖然在C#中也可以模擬C++通過靜態存儲區或棧複製來完成通信,但這些方式給人的感覺比較笨拙不夠優雅,所以,當你找不到更好的辦法來實現類間通信時,就用事件機制吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章