深入理解信号槽(二)
多對(duì)多
下一個(gè)問題是,我們能夠在點(diǎn)擊一次重新載入按鈕之后做多個(gè)操作嗎?也就是讓信號(hào)和槽實(shí)現(xiàn)多對(duì)多的關(guān)系?
實(shí)際上,我們只需要利用一個(gè)普通的鏈表,就可以輕松實(shí)現(xiàn)這個(gè)功能了。比如,如下的實(shí)現(xiàn):
class MultiAction : public AbstractAction// ...an action that is composed of zero or more other actions;// executing it is really executing each of the sub-actions { public:// ...virtual void execute(); private:std::vector<AbstractAction*> actionList_;// ...or any reasonable collection machinery };void MultiAction::execute() {// call execute() on each action in actionList_std::for_each( actionList_.begin(),actionList_.end(),boost::bind(&AbstractAction::execute, _1) ); }這就是其中的一種實(shí)現(xiàn)。不要覺得這種實(shí)現(xiàn)看上去沒什么水平,實(shí)際上我們發(fā)現(xiàn)這就是一種相當(dāng)簡(jiǎn)潔的方法。同時(shí),不要糾結(jié)于我們代碼中的 std:: 和 boost:: 這些命名空間,你完全可以用另外的類,強(qiáng)調(diào)一下,這只是一種可能的實(shí)現(xiàn)。現(xiàn)在,我們的一個(gè)動(dòng)作可以連接多個(gè) button 了,當(dāng)然,也可以是別的 Action 的使用者。現(xiàn)在,我們有了一個(gè)多對(duì)多的機(jī)制。通過將 AbstractAction* 替換成 boost::shared_ptr<AbstractAction>,可以解決 AbstractAction 的歸屬問題,同時(shí)保持原有的多對(duì)多的關(guān)系。
這會(huì)有很多的類!
如果你在實(shí)際項(xiàng)目中使用上面的機(jī)制,很多就會(huì)發(fā)現(xiàn),我們必須為每一個(gè) action 定義一個(gè)類,這將不可避免地引起類爆炸。至今為止,我們前面所說的所有實(shí)現(xiàn)都存在這個(gè)問題。不過,我們之后將著重討論這個(gè)問題,現(xiàn)在先不要糾結(jié)在這里啦!
特化!特化!
當(dāng)我們開始工作的時(shí)候,我們通過將每一個(gè) button 賦予不同的 action,實(shí)現(xiàn) Button 類的重用。這實(shí)際是一種特化。然而,我們的問題是,action 的特化被放在了固定的類層次中,在這里就是這些 Button 類。這意味著,我們的 action 很難被更大規(guī)模的重用,因?yàn)槊恳粋€(gè) action 實(shí)際都是與 Button 類綁定的。那么,我們換個(gè)思路,能不能將這種特化放到信號(hào)與槽連接的時(shí)候進(jìn)行呢?這樣,action 和 button 這兩者都不必進(jìn)行特化了。
函數(shù)對(duì)象
將一個(gè)類的函數(shù)進(jìn)行一定曾度的封裝,這個(gè)思想相當(dāng)有用。實(shí)際上,我們的 Action 類的存在,就是將 execute() 這個(gè)函數(shù)進(jìn)行封裝,其他別無用處。這在 C++ 里面還是比較普遍的,很多時(shí)候我們用 ++ 的特性重新封裝函數(shù),讓類的行為看起來就像函數(shù)一樣。例如,我們重載 operator() 運(yùn)算符,就可以讓類看起來很像一個(gè)函數(shù):
class AbstractAction { public:virtual void operator()() = 0; };// using an action (given AbstractAction& action) action();這樣,我們的類看起來很像函數(shù)。前面代碼中的 for_each 也得做相應(yīng)的改變:
// previously std::for_each( actionList_.begin(),actionList_.end(),boost::bind(&AbstractAction::execute, _1) ); // now std::for_each( actionList_.begin(),actionList_.end(),boost::bind(&AbstractAction::operator(), _1) );現(xiàn)在,我們的 Button::clicked() 函數(shù)的實(shí)現(xiàn)有了更多的選擇:
// previously action_->execute();// option 1: use the dereferenced pointer like a function (*action_)();// option 2: call the function by its new name action_->operator()();看起來很麻煩,值得這樣做嗎?
下面我們來試著解釋一下信號(hào)和槽的目的。看上去,重寫 operator() 運(yùn)算符有些過分,并不值得我們?nèi)ミ@么做。但是,要知道,在某些問題上,你提供的可用的解決方案越多,越有利于我們編寫更簡(jiǎn)潔的代碼。通過對(duì)一些類進(jìn)行規(guī)范,就像我們要讓函數(shù)對(duì)象看起來更像函數(shù),我們可以讓它們?cè)谀承┉h(huán)境下更適合重用。在使用模板編程,或者是 Boost.Function,bind 或者是模板元編程的情形下,這一點(diǎn)尤為重要。
這是對(duì)無需更多特化建立信號(hào)槽連接重要性的部分回答。模板就提供了這樣一種機(jī)制,讓添加了特化參數(shù)的代碼并不那么難地被特化,正如我們的函數(shù)對(duì)象那樣。而模板的特化對(duì)于使用者而言是透明的。
松耦合
現(xiàn)在,讓我們回顧一下我們之前的種種做法。
我們執(zhí)著地尋求一種能夠在同一個(gè)地方調(diào)用不同函數(shù)的方法,這實(shí)際上是 C++ 內(nèi)置的功能之一,通過 virtual 關(guān)鍵字,當(dāng)然,我們也可以使用函數(shù)指針實(shí)現(xiàn)。當(dāng)我們需要調(diào)用的函數(shù)沒有一個(gè)合適的簽名,我們將它包裝成一個(gè)類。我們已經(jīng)演示了如何在同一地方調(diào)用多個(gè)函數(shù),至少我們知道有這么一種方法(但這并不是在編譯期完成的)。我們實(shí)現(xiàn)了讓“信號(hào)發(fā)送”能夠被若干個(gè)不同的“槽”監(jiān)聽。
不過,我們的系統(tǒng)的確沒有什么非常與眾不同的地方。我們來仔細(xì)審核一下我們的系統(tǒng),它真正不同的是:
- 定義了兩個(gè)不同的術(shù)語:“信號(hào)”和“槽”;
- 在一個(gè)調(diào)用點(diǎn)(信號(hào))與零個(gè)或者多個(gè)回調(diào)(槽)相連;
- 連接的焦點(diǎn)從提供者處移開,更多地轉(zhuǎn)向消費(fèi)者(也就是說,Button 并不需要知道如何做是正確的,而是由回調(diào)函數(shù)去告知 Button,你需要調(diào)用我)。
但是,這樣的系統(tǒng)還遠(yuǎn)達(dá)不到松耦合的關(guān)系。Button 類并不需要知道 Page 類。松耦合意味著更少的依賴;依賴越少,組件的可重用性也就越高。
當(dāng)然,肯定需要有組件同時(shí)知道 Button 和 Page,從而完成對(duì)它們的連接。現(xiàn)在,我們的連接實(shí)際是用代碼描述的,如果我們不用代碼,而用數(shù)據(jù)描述連接呢?這么一來,我們就有了松耦合的類,從而提高二者的可重用性。
新的連接模式
什么樣的連接模式才算是非代碼描述呢?假如僅僅只有一種信號(hào)槽的簽名,例如 void (*signature)(),這并不能實(shí)現(xiàn)。使用散列表,將信號(hào)的名字映射到匹配的連接函數(shù),將槽的名字映射到匹配的函數(shù)指針,這樣的一對(duì)字符串即可建立一個(gè)連接。
然而,這種實(shí)現(xiàn)其實(shí)包含一些“握手”協(xié)議。我們的確希望具有多種信號(hào)槽的簽名。在信號(hào)槽的簡(jiǎn)短回答中我們提到,信號(hào)可以攜帶附加信息。這要求信號(hào)具有參數(shù)。我們并沒有處理成員函數(shù)與非成員函數(shù)的不同,這又是一種潛在的函數(shù)簽名的不同。我們還沒有決定,我們是直接將信號(hào)連接到槽函數(shù)上,還是連接到一個(gè)包裝器上。如果是包裝器,這個(gè)包裝器需要已經(jīng)存在呢,還是我們?cè)谛枰獣r(shí)自動(dòng)創(chuàng)建呢?雖然底層思想很簡(jiǎn)單,但是,真正的實(shí)現(xiàn)還需要很好的努力才行。似乎通過類名能夠創(chuàng)建對(duì)象是一種不錯(cuò)的想法,這取決于你的實(shí)現(xiàn)方式,有時(shí)候甚至取決于你有沒有能力做出這種實(shí)現(xiàn)。將信號(hào)和槽放入散列表需要一種注冊(cè)機(jī)制。一旦有了這么一種系統(tǒng),前面所說的“有太多類了”的問題就得以解決了。你所需要做的就是維護(hù)這個(gè)散列表的鍵值,并且在需要的時(shí)候?qū)嵗悺?/p>
給信號(hào)槽添加這樣的能力將比我們前面所做的所有工作都困難得多。在由鍵值進(jìn)行連接時(shí),多數(shù)實(shí)現(xiàn)都會(huì)選擇放棄編譯期類型安全檢查,以滿足信號(hào)和槽的兼容。這樣的系統(tǒng)代價(jià)更高,但是其應(yīng)用也遠(yuǎn)遠(yuǎn)高于自動(dòng)信號(hào)槽連接。這樣的系統(tǒng)允許實(shí)例化外部的類,比如 Button 以及它的連接。所以,這樣的系統(tǒng)有很強(qiáng)大的能力,它能夠完成一個(gè)類的裝配、連接,并最終完成實(shí)例化操作,比如直接從資源描述文件中導(dǎo)出的一個(gè)對(duì)話框。既然它能夠憑借名字使函數(shù)可用,這就是一種腳本能力。如果你需要上面所說的種種特性,那么,完成這么一套系統(tǒng)絕對(duì)是值得的,你的信號(hào)槽系統(tǒng)也會(huì)從中受益,由數(shù)據(jù)去完成信號(hào)槽的連接。
對(duì)于不需要這種能力的實(shí)現(xiàn)則會(huì)忽略這部分特性。從這點(diǎn)看,這種實(shí)現(xiàn)就是“輕量級(jí)”的。對(duì)于一個(gè)需要這些特性的庫而言,完整地實(shí)現(xiàn)出來就是一個(gè)輕量級(jí)實(shí)現(xiàn)。這也是區(qū)別這些實(shí)現(xiàn)的方法之一。
本文轉(zhuǎn)自 FinderCheng 51CTO博客,原文鏈接:?
http://blog.51cto.com/devbean/424778
總結(jié)
以上是生活随笔為你收集整理的深入理解信号槽(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AIDL 客户端与服务端的双向通信
- 下一篇: scvmm live migration