前面講的定時(shí)炸彈的一種最基本的實(shí)現(xiàn)方式,但是條條大路通羅馬,總有大神出歪招,C語言的核心技能之一即將到達(dá)戰(zhàn)場,函數(shù)指針(這個(gè)詞有的童鞋可能比較陌生,也可能再別人程序上見到過,但是這種跳來跳去的操作總感覺和goto有的一拼,復(fù)雜難懂),對(duì)于指針大家用的更多的是變量指針,尤其是和數(shù)組一起用的時(shí)候,那種完美的數(shù)據(jù)流向真的很讓人沉醉,其實(shí)假如你真正的理解了函數(shù)指針的意義,那么你就知道他是C語言實(shí)現(xiàn)程序流向控制最核心的技術(shù)之一。
夸得太多,其實(shí)現(xiàn)在開始講理論總是一個(gè)腦袋兩個(gè)大,還是先上例子,定時(shí)炸彈狀態(tài)表模式,如何用一個(gè)表來表示所有可能執(zhí)行的操作呢?
我們把狀態(tài)和事件映射成一個(gè)二維的表,動(dòng)作在表中定義,這樣就是可以做到最精細(xì)粒度的分解。 基于以上理論,我們實(shí)際把狀態(tài)機(jī)和事件聯(lián)合,映射成了一張狀態(tài)表,然后你需要構(gòu)造一個(gè)基于狀態(tài)表的通用事件處理器,其構(gòu)造如下:
基于上面的構(gòu)造圖,我們?nèi)绾握嬲么a來實(shí)現(xiàn)它,我們來看一下他具體實(shí)現(xiàn)的代碼,如下:
1.第一步,定義事件結(jié)構(gòu)體,根據(jù)上面的圖,代碼如下:
2.第二步,狀態(tài)表應(yīng)該是一個(gè)二維數(shù)組,在表中我們希望存有的是具體執(zhí)行的上下文,也就是函數(shù),但是函數(shù)本身并不能作為數(shù)據(jù)被存入表中,但是函數(shù)指針可以當(dāng)做數(shù)據(jù)被存入表中,而我們可以通過函數(shù)指針類型的變量來引用具體函數(shù),從而實(shí)現(xiàn)上下文的執(zhí)行,我們需要定義一個(gè)函數(shù)指針類型,如下:
3.第三部,有了前面的鋪墊,這里開始定義一個(gè)狀態(tài)表,如下:
4.第四部,實(shí)現(xiàn)ctor,init,dispatch三個(gè)構(gòu)造函數(shù),如下:
5.這里我們還需要定義一個(gè)狀態(tài)轉(zhuǎn)換宏,如下:
小插曲:很久很久以前,我在思考一個(gè)問題,為什么要定義一個(gè)如此復(fù)雜的宏,當(dāng)所有的代碼都是需要我們自己寫的時(shí)候,我們不光要明白一處代碼的含義,還要精確地設(shè)計(jì)這里的代碼,這時(shí)候用不用宏其實(shí)都可以,但是假如我們?cè)O(shè)計(jì)的代碼是給別人來用的,他并不需要精確到設(shè)計(jì)的細(xì)節(jié),而是只需要理解這句代碼的含義,也就足夠了,這就是我們的對(duì)于軟件的設(shè)計(jì)理念不同所導(dǎo)致的,我們總想著徹底研究明白別人的代碼,別人設(shè)計(jì)的初衷其實(shí)是讓你快速的應(yīng)用,并不是徹底的理解。
徹底的理解和應(yīng)用是兩個(gè)概念,年輕時(shí)候我鉆入的牛角尖就在這里,浪費(fèi)了大把的精力從入門到放棄,關(guān)于對(duì)軟件的理念,從零重寫一整套代碼,遠(yuǎn)沒有熟練應(yīng)用一個(gè)成熟的系統(tǒng)更有價(jià)值,我們不是科學(xué)家,只是engineer,我們不生產(chǎn)構(gòu)架,只是構(gòu)架的搬運(yùn)工,舉個(gè)簡單的例子,例如你對(duì)安卓系統(tǒng)的理解和你對(duì)安卓系統(tǒng)的應(yīng)用,仔細(xì)想想,其實(shí)是兩個(gè)不同的命題,只是我們把命題弄反了,又扯遠(yuǎn)了。。。見諒。
這里的dispatch是不是對(duì)比進(jìn)階1中基本被精簡很多了呢?這就是狀態(tài)表帶給我們的好處,接下來我們看一下bomb應(yīng)用state如何構(gòu)造真實(shí)的狀態(tài)表,及實(shí)例代碼,如下:
1.main函數(shù)開始處,先會(huì)執(zhí)行bomb的ctor函數(shù),如下:
每一處代碼都需要靜下心來,細(xì)細(xì)研究,慢慢的品味。
2.執(zhí)行完構(gòu)造函數(shù)以后,那么就真正意義上構(gòu)造好一個(gè)statetable實(shí)例對(duì)象了,接下來需要初始化操作,讓我們的狀態(tài)表開始從初始狀態(tài)進(jìn)入到運(yùn)行階段,做好接收事件的準(zhǔn)備,調(diào)用順序如下:
3.基于產(chǎn)生的事件,調(diào)用dispatch函數(shù)處理相應(yīng)的事件,如下:
關(guān)于基于狀態(tài)表的方式設(shè)計(jì)的優(yōu)點(diǎn),這里不想總結(jié)了,留給大家自行總結(jié),我們來說說他的缺點(diǎn):
1.第一,他看上去不怎么像狀態(tài)機(jī),而是變成狀態(tài)事件的一個(gè)聯(lián)合體,說白了把整個(gè)程序的精細(xì)粒度打的更散,你需要更多地狀態(tài)表結(jié)構(gòu)函數(shù),遠(yuǎn)多于狀態(tài)機(jī)。
2.其二,擴(kuò)展?fàn)顟B(tài)機(jī)里面是有進(jìn)入和退出動(dòng)作的, 這里該怎么加,它看上去更像Mearly機(jī),但有不符合Mearly機(jī)的定義,真正基于這個(gè)模型要擴(kuò)展有點(diǎn)難。
3.第三,當(dāng)發(fā)生一個(gè)注冊(cè)狀態(tài)表之外的事件時(shí),你需要明確的定義他,并忽略他,否則將會(huì)產(chǎn)生斷言,導(dǎo)致系統(tǒng)運(yùn)行出錯(cuò),這要求你在設(shè)計(jì)的時(shí)候要提一萬個(gè)心,防止以外的發(fā)生,異常處理不夠健壯。
其實(shí)還有很多細(xì)節(jié),大家可以洗洗的品,比如狀態(tài)表結(jié)構(gòu)的聲明是一個(gè)函數(shù)指針,賦值的卻是一個(gè)二維數(shù)組,他們之間是如何發(fā)生轉(zhuǎn)換的,細(xì)細(xì)品很有意思,到這里我還特別想講講函數(shù)指針,但是這里還是把話咽到肚子里,留給大家自己思考和體會(huì),在我們的后面設(shè)計(jì)通用的QEP事件處理器,他會(huì)再次粉末登場~!
早這里,進(jìn)階2就結(jié)束了,下一章講講面向?qū)ο蟮臓顟B(tài)機(jī),這個(gè)版本是一個(gè)C++版本,也是我們的GUI事件驅(qū)動(dòng)型系統(tǒng)應(yīng)用的根基,C語言實(shí)現(xiàn)多態(tài)還是有些復(fù)雜。到這里講先告一段落了,進(jìn)階3再見~!