• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            每天早晨叫醒你的不是鬧鐘,而是夢想

              C++博客 :: 首頁 :: 聯(lián)系 :: 聚合  :: 管理
              62 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

            常用鏈接

            留言簿(1)

            我參與的團隊

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            關(guān)于低耦合的消息傳遞,實現(xiàn)的方式有很多,哪種方法更好與具體的使用環(huán)境有關(guān),本文使用試錯的方法,逐步探索達成這一目的具體方式,并理解實現(xiàn)方式背后的原因。

            面向?qū)ο蟮南到y(tǒng)當(dāng)中,不可避免的有大量的類間消息傳遞的需求:一個類需要通知另一個或幾個類做些什么。

            這種類間消息傳遞,簡單的說,就是調(diào)用其他類的方法。

            如下:

            1void A::OnMessageXX()
            2{
            3         B::GetInstance()->DoSomething();
            4
            5}

            6
            7

             

            在這里,類A需要通知類B做些事情。這種調(diào)用在所有的面向?qū)ο蟪绦蛑卸际菢O其常見的。

            但是如果類A需要調(diào)用類B,就不可避免的產(chǎn)生了耦合性。雖然耦合性終歸是不可能完全避免的,但是在一定程度上降低耦合性是完全可能的。

            (至于為什么在設(shè)計中應(yīng)該盡可能降低耦合性,不在本文的探討范圍之內(nèi))

            上面的例子,我們使用了Singleton的模式,從全局作用域中獲取了B的實例,并調(diào)用了B的相關(guān)方法。使用Singleton的一個缺點是,假若我們希望對類A編寫測試代碼,我們需要做一些額外的解耦合工作。(關(guān)于編寫測試與解耦合,可以參考Robert C. Martin Series 的Working Effectively with Legacy Code一書,該書的中譯版在這 )

            我們也可以通過將B參數(shù)化的方法降低A與B間的耦合程度,像下面這樣:

            1 void A::OnMessageXX(B* pBInstance)
            2 {
            3          pBInstance->DoSomething();
            4 
            5 }
            6 
            7 

             

            現(xiàn)在的寫法要比之前的做法耦合性低,通過使用多態(tài)的方法,現(xiàn)在傳入函數(shù)的類B指針可能是另一個實現(xiàn)了B的相應(yīng)接口的派生類,A并不關(guān)心B接口背后的具體實現(xiàn)。

            但是等等,你說,現(xiàn)在對類B的耦合性雖然在A中被降低了,但是依舊存在于調(diào)用A::OnMessageXX的地方。在那里我們還是需要取得B的實例,然后傳遞給A。

            沒錯,是這樣。

            通過參數(shù)化類A的方法,我們把類A與類B間的耦合轉(zhuǎn)移了一部分到A的調(diào)用者那里。實際上總的耦合并沒有消除,只是被分解了。但是程序設(shè)計中不可能完全不存在耦合,我們需要做的是”正確”,而不是”完美”。類A的耦合性降低了,使得我們在未來需求變更的時候,類A有更大的可能性不需要被修改,并且對功能的擴展更加友好,這就達成了我們的目標(biāo)了。

            基于上述做法,如果我們在未來擴展是派生出一個B的子類,override相關(guān)的方法,那么類A的代碼基本是不需要修改的。

            不過,問題是,假若A::OnMessageXX中,并不僅僅需要對類B發(fā)出消息,還需要對一系列相關(guān)的類B1,B2,B3等等發(fā)出消息呢?

            哦,或許我們可以這樣做:

             

            void A::OnMessageXX(const std::list<B*>& lstBInstances)
            {
                     
            for (std::list<B*>::const_iterator itr = lstBInstances.begin();
                               itr 
            != lstBInstances.end();
                               
            ++itr)
                     
            {
                               (
            *itr)->DoSomething();

                     }

            }



            是的,上面這是一種做法,有一系列B的對象需要被通知到,所以我們可以用一個列表把他們串起來,然后在循環(huán)中通知他們?nèi)ジ苫睢2贿^這樣做的前提是,這一系列B對象都是派生自一個公共基類B,有共通的接口;此外,我們需要在A的OnMessageXX被調(diào)用之前構(gòu)造一個需要接受通知的B對象列表。

            當(dāng)A需要通知B,C,D等一系列沒有公共接口的對象的時候,上面的這種做法就無法處理了。

            對于B、C、D等需要由A來調(diào)用的類來說,它們需要在A通知它們的時候,做一些特定的事情。而又A則是在某些特定的時刻需要通知B、C、D。這樣,我們可以把問題看成一個消息響應(yīng)機制。

            B、C、D可以在A的某些事件上注冊一些回調(diào)函數(shù),當(dāng)事件發(fā)生時,A確保注冊該事件的函數(shù)被調(diào)用到。

            如下:

            typedef void(callback*)();

            class A {

            public:

                     enum EventIds {

                     EVENT_MSG1,

                     EVENT_MSG2,

            };

            void RegisterEvent(int nEventId, callback pfn);

            private:

            callback m_pfnCallback;

            };

            現(xiàn)在,B可以調(diào)用A::RegisterEvent注冊一個事件,并傳遞一個函數(shù)指針給A。

            當(dāng)A中發(fā)生了注冊的事件時,這個函數(shù)指針會被回調(diào)到。

            不過這種簡單的做法適應(yīng)性很差:

            1、  不能支持單個事件的多個callback (可能有很多類都需要注冊該事件,并在事件發(fā)生時依次被回調(diào))

            2、  不能支持多個事件的同時存在

            3、  回調(diào)函數(shù)沒有參數(shù)’

            針對問題1,2,我們可以使用一個事件映射解決問題,做法如下:

            typedef int EventId;

            typedef void (callback*)();

            typedef std::list<callback> CallbackList;

            typedef std::map<EventId, CallbackList> CallbackMap;

            現(xiàn)在這個數(shù)據(jù)結(jié)構(gòu)就能夠支持多個event同時存在,且每個event都可以支持多個回調(diào)函數(shù)了。

            但是這種用法依舊很不方便,如果類B想要注冊A上的一個事件,他需要定義一個 callback類型的函數(shù),并把這個函數(shù)的地址傳遞給A。問題是,往往我們希望類B的回調(diào)函數(shù)在被調(diào)用到的時候,對類B中的數(shù)據(jù)和狀態(tài)進行修改,而一個單獨的函數(shù),若想獲得/修改B中的狀態(tài),則必須要與類B緊密耦合。(通過獲取全局對象,或者Singleton的方式)

            這種緊密耦合引發(fā)我們的思考,能否在Callback中同時包含類B的指針與類B的成員函數(shù)。

            答案是肯定的:泛型回調(diào) 就可以做到這一點。關(guān)于泛型回調(diào)(Generic callback)的信息,在Herb Sutter的Exceptional C++ Style 的35條中有詳細介紹。

            一下比較簡單的泛型回調(diào)的定義如下:

            class callbackbase {

            public:

            virtual void operator()() const {};

            virtual ~callbackbase() = 0 {};

            };

            template <class T>

            class callback : public callbackbase {

            public:

            typedef void (T::*Func)();

            callback(T& t, Func func) : object(t), f(func) {}     // 綁定到實際對象

            void operator() () const { (object->*f)(); }              // 調(diào)用回調(diào)函數(shù)

            private:

            T* object;

            Func f;

            };

            有了這種泛型回調(diào)類,我們就可以將類B的實例與B的成員回調(diào)函數(shù)綁定在一起注冊到容器當(dāng)中了,而不必再被如何在普通函數(shù)中修改B對象狀態(tài)的問題所困擾了。不過回調(diào)函數(shù)的參數(shù)問題依舊。如果想支持參數(shù),我們不得不對每一種參數(shù)類型做一個不同的typedef,像上面定義的這樣 typedef void (T::*Func)();(如:typedef void (T::*Func)(int);)

            一種解決方案是借助于Any(一種任意類型類)進行參數(shù)傳遞。

            但是還有更完善的解決方案,不需要id號,也不需要泛型回調(diào),Ogre采用Listener的方式實現(xiàn)的類間消息傳遞不僅可以支持單個類B對類A中某個事件的單次/多次注冊,也可以支持類B、C、D對同一個事件的注冊。而且可以完美的解決參數(shù)傳遞問題。

            具體的方案如下:

             1class A {
             2public:
             3         class Listener 
             4        {
             5           public:
             6
             7                   virtual void OnMessageXX(int param1, float param2) = 0;
             8
             9                   virtual void OnMessageYY(int param1, const std::string& param2) = 0;
            10
            11        }
            ;
            12
            13void registerListener(Listener* obj) 
            14
            15   m_lstListener.push_back(obj); 
            16}

            17
            18void removeListener(Listener* obj)
            19{
            20         ListenerList::iterator itr = std::find(m_lstListener.begin(), m_lstListener.end(), obj); 
            21
            22         if (itr != m_lstListener.end())
            23                   m_lstListener.erase(itr);
            24}

            25
            26private:
            27         typedef std::list<Listener*> ListenerList;
            28
            29         ListenerList m_lstListeners;
            30}
            ;
            31
            32

             

            有了以上定義,當(dāng)類A收到某個消息XX之后,只需遍歷m_lstListeners列表,調(diào)用所有列表成員的OnMessageXX即可。

            而所有注冊A的消息的類,都必須從A::Listener派生一個類,在它感興趣的消息處理函數(shù)中做出相應(yīng)處理,而對不感興趣的消息,只需設(shè)為空函數(shù)即可。

            一個簡單的類B的定義如下:

            class B {

            public:

                     friend class BListener;

                     class BListener : public A::Listener {

                     public:

                               BListener(B* pBInstance) : m_pBInstance(pBInstance) {}

                               virtual void OnMessageXX(int param1, float param2)

            { m_pBInstance->DoSomething(); }

                               virtual void OnMessageYY(int param1, const std::string& param2) {}

                     private:

                               B* m_pBInstance;

            };

            explicit B(A* pAInstance) : m_pAInstance(pAInstance)

            {

            m_pListener(new BListener(this));

            m_pAInstance->registerListener(m_pListener);

            }

                     ~B() { m_pAInstance->removeListener(m_pListener); delete m_pListener; }

            void DoSomething();

            private:

                     BListener* m_pListener;

            }

            類B在創(chuàng)建自身實例時,接受一個A的指針(這是合理的,因為類B需要監(jiān)聽類A的消息,理應(yīng)知道A的存在),并創(chuàng)建一個派生自A::Listener 的監(jiān)聽者對象,并把自身的指針傳遞給該對象,以使得該監(jiān)聽者改變類B的狀態(tài),而后類B將創(chuàng)建好的監(jiān)聽者對象加入到A的監(jiān)聽者列表中。

            在B進行析構(gòu)的時候,需要從A中刪除自己注冊的監(jiān)聽者。而后將該對象釋放。

            這種做法的好處:

            1、  類B(以及類C等)對類A實現(xiàn)了信息隱藏,類A不再關(guān)注任何需要監(jiān)聽它自身消息的其他類,只需關(guān)注其自身的狀態(tài)。從而減低了類A與其他與之關(guān)聯(lián)的類之間的耦合。(類A不必再費盡心機的去獲取B的指針,不管是通過全局變量,還是Singleton,還是參數(shù),還是類成員變量,都不再需要了,A只關(guān)心在 Listener中定義好的一組接口即可)而且,如果有必要類B可以對同一個消息注冊多次,且可以對同一消息有不同的反應(yīng)(通過定義不同的 BListener實現(xiàn)達到這一目的),只需在B不再需要監(jiān)聽相關(guān)消息時將所注冊過的對象注銷掉即可。

            2、  由于1中所述,類A的實現(xiàn)無需關(guān)心類B的實現(xiàn),因此類A的邏輯中不需要包含任何類B的方法調(diào)用,從而,類A的cpp文件中,無需包含類B的頭文件,(可能還包括類C,D等等,此處類B指代需要根據(jù)類A狀態(tài)而做出動作的類)從而降低編譯時間,這是解耦合所帶來的附加好處。

            3、  同樣是解耦合帶來的好處:因為無需關(guān)注類B等等其他類的實現(xiàn),類A的代碼邏輯變得更加清晰,并且減少未來邏輯需求變更的改動所需要付出的代價(邏輯變更可能需要更改接口,需要增加狀態(tài)判斷,無論是調(diào)試時間還是編譯時間都是不可忽視的代價)。

             

            本文來自CSDN博客,轉(zhuǎn)載請標(biāo)明出處:http://blog.csdn.net/zougangx/archive/2009/07/30/4395775.aspx

            posted on 2011-05-12 18:12 沛沛 閱讀(382) 評論(0)  編輯 收藏 引用 所屬分類: C++
            蜜臀久久99精品久久久久久小说 | 亚洲精品tv久久久久久久久| 国产一区二区三区久久| 久久99热只有频精品8| 久久国产乱子伦免费精品| 亚洲精品tv久久久久久久久| 国产精品久久久久久久app| 久久无码专区国产精品发布| 精品国产青草久久久久福利| 日日躁夜夜躁狠狠久久AV| 久久婷婷五月综合色高清| 久久99国产乱子伦精品免费| 国产精品久久成人影院| 91亚洲国产成人久久精品| 久久天天躁狠狠躁夜夜2020| 伊人久久大香线蕉无码麻豆| 久久久久免费精品国产| AV无码久久久久不卡蜜桃| 97久久精品人人澡人人爽 | 国产精品丝袜久久久久久不卡| 国产福利电影一区二区三区久久老子无码午夜伦不 | 日日狠狠久久偷偷色综合0| 久久久SS麻豆欧美国产日韩| 久久久久久无码Av成人影院| 日本久久久久久中文字幕| 手机看片久久高清国产日韩 | 久久亚洲国产精品123区| 久久香综合精品久久伊人| www.久久热.com| 亚洲国产成人久久笫一页| 天天躁日日躁狠狠久久| 久久婷婷国产麻豆91天堂| 久久这里的只有是精品23| 久久se精品一区精品二区| 欧美午夜精品久久久久久浪潮| 久久精品a亚洲国产v高清不卡| 国产精品免费久久久久影院| 久久精品国产亚洲av高清漫画| 久久久WWW成人免费精品| 久久久久亚洲AV无码永不| 久久久久香蕉视频|