STATE模式———履胡之腸涉胡血,懸胡青天上,埋胡紫塞旁。 junguo STATE模式的中文名稱是狀態(tài)模式。在《設(shè)計模式》一書中的定義是:允許一個對象在其內(nèi)部狀態(tài)改變的時候改變它的行為。對象看起來似乎修改了它的類(中文譯書上的原話,不過我覺得這句話應(yīng)該翻譯成——對象顯現(xiàn)出來的是改變了它所屬的類)??戳硕x還是感覺有些抽象,好的,我們還是通過一個例子來學(xué)習(xí)該模式。還是先從情節(jié)設(shè)計開始: 嚴風(fēng)吹霜海草凋,筋干精堅胡馬驕。 漢家戰(zhàn)士三十萬,將軍兼領(lǐng)霍嫖姚。 流星白羽腰間插,劍花秋蓮光出匣。 天兵照雪下玉關(guān),虜箭如沙射金甲。 云龍風(fēng)虎盡交回,太白入月敵可摧。 敵可摧,旄頭滅,履胡之腸涉胡血。 懸胡青天上,埋胡紫塞旁。 胡無人,漢道昌。 李白這首詩豪氣萬丈的詩篇,描述的是漢驃騎將軍霍去病率領(lǐng)大軍,出隴西,突襲匈奴部落的戰(zhàn)爭場面,千載之下,猶讓人感覺豪邁異常。匈奴和漢朝的爭端從漢高祖劉邦開始。楚漢之爭過后,劉邦統(tǒng)一了中國。此時,匈奴部族也被冒頓(這兩字發(fā)音應(yīng)為mo du)單于統(tǒng)一了起來。匈奴逐漸強大的同時,開始窺覷漢朝的疆土。冒頓單于領(lǐng)軍進犯太原,包圍晉陽。劉邦親率30萬大軍進擊匈奴,不期竟中匈奴之計,被困于白登。幸虧用陳平之計,送重禮于冒頓夫人,走枕邊風(fēng)路線,才讓匈奴大軍讓開一條路,逃了出來,狼狽之極。這場戰(zhàn)役的結(jié)果就是漢朝采取和親政策,送漢宗室之女給匈奴王為妾,并借機進貢。但這也避免不了匈奴侵犯漢族邊界,殺戮漢民,搶奪牲畜。這樣的歷史持續(xù)了數(shù)十年,直到漢武帝的時候還保持著。而雄才大略的武帝,豈能容忍此奇恥大辱。堅毅勇猛的他,籌劃著對匈奴的打擊,經(jīng)過一系列的改革(個人覺得他改革兵種,將以車兵步兵為主的軍隊改為輕騎兵為主,對這場戰(zhàn)役意義最為重大),開始了對匈奴的窮追猛打。這段歷史造就了中國歷史上兩顆耀眼的將星:衛(wèi)青,霍去病?;羧ゲ「訆Z目,他18歲的時候,隨衛(wèi)青大軍出征。他率領(lǐng)800輕騎,遠離大軍,奔赴敵人腹地,斬敵二千余人。被漢武帝封為冠軍侯。后又率1萬大軍出隴西,轉(zhuǎn)戰(zhàn)千余里,斬獲8000于人。再于同年秋天,出北地,攻祁連山,大獲全勝,斬獲三萬余人。漢朝與匈奴的最后一次大型戰(zhàn)役也由衛(wèi)青,霍去病完成?;羧ゲ∮H率5萬騎兵遠離代郡,對匈奴人窮追猛打,殲敵七萬余人。乘勝追擊到狼居胥山(今在蒙古境內(nèi)),并在此舉行祭天儀式(封狼居胥)。此時的霍去病僅僅21歲,但二年后英年早逝。在他短暫的一生中四次與匈奴做戰(zhàn),轉(zhuǎn)戰(zhàn)數(shù)千余里,滅敵十余萬人,徹底熄滅了匈奴人的囂張氣焰,也給大漢的邊境帶來了安寧。不僅如此,他還留下了“匈奴未滅,何以家為”的千古豪言。真是錚錚男兒形象,為萬世鐵骨男兒楷模。 前年看到的一部連續(xù)劇《漢武大帝》,總體感覺還行。不過太過小氣,為了一些戲劇效果,把很多歷史事件強塞了進去。如曹操為人捉刀的故事,被安排到了漢武帝身上;為了那么一段冒頓單于鳴鏑殺父的情節(jié),讓他晚生了數(shù)十年,居然他還成了漢武帝的死敵(為漢武帝安排這么一個對手,我總覺得太小覷了漢武帝)。 還有些事,本不想寫了,后來想想還是寫吧。反正咱這是亂砍,就隨性而來吧。網(wǎng)上有個臭名昭著的網(wǎng)站,叫做漢網(wǎng)。集聚著一群民族主義分子,提倡漢服(不過他們好像也不是提倡每天都穿),說這樣可以幫助中國人恢復(fù)民族自尊心。讓我想起阿Q說過的:老子原前也闊過。還有明顯意淫古人的意味,翻看歷史,我們可以看到武帝之前,漢族一直受匈奴人的欺凌。而武帝之后,國力衰落,雖然周邊的小國還算順服,但漢朝還是失去了武帝時的雄霸之氣。試想想如果當(dāng)時情況下的皇帝不是漢武,而是一個軟弱無能,如明朝的那個建文帝,估摸著漢朝內(nèi)部都搞不定。更甭談打擊匈奴了,而消滅匈奴取得成功,也和武帝破格提拔的將領(lǐng)有關(guān)。試想沒有衛(wèi)青,霍去病又會是什么情況?第一次攻擊匈奴,四路人馬出擊,兩路被?。ㄖ娘w將軍李廣被俘,而后逃脫),一路無功,只有衛(wèi)青有所斬獲。而后的多次出擊中,除衛(wèi)青,霍去病外的其他將領(lǐng)多有敗績。在電視《漢武大帝》中,李廣的死被安排成了為了成全衛(wèi)青的計策,吸引匈奴,他主動陷于匈奴包圍圈中,英勇戰(zhàn)死(這段情節(jié),描寫的匈奴人也太弱智了)。我一哥們看到這情節(jié)后,哈哈大笑。然后他津津有味的為別人講解李廣真正的死因:李廣是路癡死的。李廣出擊匈奴的戰(zhàn)爭中多次迷路,最后一次因為衛(wèi)青派人問他迷路的原因,李廣自殺身亡。李廣是漢朝名將,但我覺得他不適合那個年代。從戰(zhàn)國時代起,名將都以智略取勝。因為戰(zhàn)爭基本都以攻城拔地的形式進行,都有相對固定的戰(zhàn)爭場地。武器也以戰(zhàn)車,重騎兵為主,行動較為緩慢,主要打的是陣地戰(zhàn)。而當(dāng)時匈奴,采取的是打得過就打,打不過就跑得游擊套路(呵呵,應(yīng)該是我軍游擊戰(zhàn)的雛形了)。面對這樣的對手,更需要的就是霍去病這種有銳氣,深沉勇猛,敢于追著敵人背后猛打的將領(lǐng)(我覺得國民黨將領(lǐng)薛岳也屬于此類戰(zhàn)將)。當(dāng)漢武讓霍去病學(xué)習(xí)古兵法的時候,他的回答是:顧方略何如耳,不至學(xué)古兵法。也許正是這種藐視權(quán)威的氣概成就了霍去病,也成就了“犯大漢者,雖遠必誅”的大漢豪情。但這段歷史的演繹里離不開漢武帝,也離不開霍去病(戰(zhàn)功最著的是他,單靠衛(wèi)青可能這場戰(zhàn)爭很難如此徹底),是他們的組合成就了這段歷史。沒有這樣的組合,也許大漢天下會被匈奴的鐵蹄踐踏,而漢文明也許會象希臘文明那樣被游牧民族踐踏而消亡。歷史充滿了偶然性,歷史本身并不值得我們拿來炫耀。我覺得如今的中國人沒有什么不自信的,我也不覺得現(xiàn)在的中國人沒古代好,偉大的時代總離不開偉大的人物。也許我們的民族只是現(xiàn)在缺少一個這樣的人物,那也就不好怨天尤人了,因為身處這個時代的我們,沒有一個是偉人,我不是,你也不是,那你怪誰去?好了,不扯了,還是認真做我們的Coding生涯吧。 我們以驃姚將軍深入漠北追殲匈奴人的情節(jié)作為我們的例子,這次我們將五萬騎兵作為我們的對象。這五萬騎兵作為一個作戰(zhàn)整體,會有一些這樣的一些狀態(tài):休整,前進,攻擊。整個作戰(zhàn)過程中都在這些狀態(tài)下進行著。我們先來看看下圖: 這里我們看到一個轉(zhuǎn)換圖:共有三種狀態(tài)Advaneing(前進),Assaulting(攻擊狀態(tài)),Resing(休息狀態(tài))。三個狀態(tài)之間可以進行轉(zhuǎn)換,Advaneing和Resing可以直接轉(zhuǎn)換,Advanceing和Resing可以直接轉(zhuǎn)換到Assaulting狀態(tài),而Assaulting只能在敵軍被全殲的時候才能轉(zhuǎn)化到Resing狀態(tài)。 如何來實現(xiàn)該系統(tǒng)呢?我們還是首先用土鱉似的方式來實現(xiàn)第一個系統(tǒng),這樣更有助于看到模式的優(yōu)點。我們首先建立一個Army類,如下: #define ADVANEING 0 #define ASSAULTING 1 #define RESTING 2 Class Army { Private: int m_iState; int m_iEmptyCout; public: Army():m_iState(RESTING){} Void Advance(); Void Assault(); Void Rest(); }; Void Army::Advance() { If (m_iState == ADVANEING ) { Cout << “Are Advaning!” << endl; } Eles if ( m_iState == ASSAULTING ) { Cout << “sorry! Are assauling!Can’t Advace” << endl; } Else if( m_iState == RESTING ) { m_iState = ADVANING Cout << “ok!Go!” << endl; } } Void Army:: Assault () { If (m_iState == ADVANEING ) { m_iEmptyCout = 100; m_iState == ASSAULTING; Cout << “ok!Assault!” << endl; } Eles if ( m_iState == ASSAULTING ) { m_iEmptyCount -= 100; Cout << “Are assauling!” << endl; } Else if( m_iState == RESTING ) { m_iEmptyCout = 100; m_iState = ASSAULTING; Cout << “ok! Assault!” << endl; } } Void Army:: Rest () { If (m_iState == ADVANEING ) { m_iState == RESTING; Cout << “ok!Rest!” << endl; } Eles if ( m_iState == ASSAULTING ) { Cout << “Are assauling!can’t Rest” << endl; } Else if( m_iState == RESTING ) { Cout << “Are Resing!” << endl; } } 好了這樣我們的類就完成了,雖然看起來有些雜亂,但運行應(yīng)該沒有什么問題。這樣完成雖說土了一些,但事實上并不影響它的正常運行。但我們需要考慮的一個問題是:當(dāng)需求變化的時候,我們的程序該如何去改?軟件界的一個規(guī)律就是需求一直在變更,變更伴隨著軟件的生存到死亡的過程。如今流行的設(shè)計模式,重構(gòu),測試驅(qū)動開發(fā)等技術(shù)目的都是為了適應(yīng)需求的變更,而將程序修改的難度降到最低來。所以我們來考慮這樣的情況,由于驃騎將軍取得了大勝,舉行了祭天儀式,祭天儀式中戰(zhàn)士興奮度提高,殺敵熱情暴增。所以驃騎決定將這個儀式加入到戰(zhàn)斗安排中,當(dāng)取得勝利的時候,舉行祭天儀式。而這又是一個新的狀態(tài),該狀態(tài)只有在Rest狀態(tài)下才能切換過去,我們該如何去修改程序呢?以目前的做法,我們需要在每個函數(shù)中添加條件,修改函數(shù),這樣又與我們在策略模式中提到的規(guī)則“一個模塊對擴展應(yīng)該是開放的,而對修改應(yīng)該是關(guān)閉的”背道而馳了。怎么解決呢?還是同樣的方法:提煉出一個類。同樣為了解決動態(tài)改變狀態(tài)的需求,我們還應(yīng)該記著另一個規(guī)則:盡量針對接口編程,而不要針對實現(xiàn)編程。閑言少敘,我們還是看類圖,這樣來的快一些。 從類圖,我們可以看到Army類中擁有一個Station類的對象,它所有的操作將通過該對象來實現(xiàn)。是不是發(fā)覺和策略模式很相似?先不說這個,我們先看完例子再說。看看具體代碼: 我們首先看以下State接口,很簡單,就是幾個純虛函數(shù)。 class State { public: virtual void Advance() = 0; virtual void Assault() = 0; virtual void Rest() = 0; virtual void Fiesta() = 0; }; 我們再來看一下AdvanceState,我們看到在AdvanceState中有一個Army對象的指針,是因為需要在內(nèi)部修改狀態(tài),具體的代碼中可以看到。 class AdvanceState : public State { private: Army *m_pArmy; public: AdvanceState(Army *pArmy); virtual void Advance(); virtual void Assault(); virtual void Rest() ; virtual void Fiesta(); }; 我們再來看一下AdvanceState的具體實現(xiàn): AdvanceState::AdvanceState(Army *pArmy):m_pArmy(pArmy){} void AdvanceState::Advance() { cout << "Be in Advancing!" << endl; } void AdvanceState::Assault() { //設(shè)置假想的敵人數(shù) m_pArmy->SetEmptyCount(200); cout << "Ok!Assault!" << endl; m_pArmy->SetState(m_pArmy->GetAssaultState()); } void AdvanceState::Rest() { cout << "OK!Rest!" << endl; m_pArmy->SetState(m_pArmy->GetRestState()); } void AdvanceState::Fiesta() { cout << "sorry!can‘t Fiesta!" << endl; } 很簡單了,就是根據(jù)當(dāng)前狀態(tài)來處理各個函數(shù)。我們看到有這樣的函數(shù)m_pArmy->SetState(m_pArmy->GetRestState());是用來修改Army所處的狀態(tài)的。在Army類中,我們可以看到它的具體實現(xiàn)。其它幾個狀態(tài)類的實現(xiàn)類同,就不房到這里了,感興趣的可以到附件中自己找。 我們再來看看Army類的定義: class State; class Army { private: State* m_pState; //保存各個狀態(tài)指針便于使用,當(dāng)有新的狀態(tài)填加的時候,我們也需要在此處添加 State* m_pAdvanceState; State* m_pAssaultState; State* m_pRestState; State* m_pFiestaState; int m_iEmptyCount; public: Army(); void SetState(State *pState); State* GetAdvanceState(); State* GetAssaultState(); State* GetRestState(); State* GetFiestaState(); void Advance(); void Assault(); void Rest(); void Fiesta(); void SetEmptyCount(int iEmptyCount){m_iEmptyCount = iEmptyCount;} int GetEmptyCount(){return m_iEmptyCount;} }; 它的實現(xiàn): Army::Army() { m_pAdvanceState = new AdvanceState(this); m_pAssaultState = new AssaultState(this); m_pRestState = new RestState(this); m_pFiestaState = new FiestaState(this); m_pState = m_pRestState; m_iEmptyCount = 0; } void Army::SetState(State *pState) { m_pState = pState; } State* Army::GetAdvanceState() {return m_pAdvanceState;} State* Army::GetAssaultState() {return m_pAssaultState;} State* Army::GetRestState() {return m_pRestState;} State* Army::GetFiestaState() {return m_pFiestaState;} void Army::Advance() {m_pState->Advance();} void Army::Assault() {m_pState->Assault();} void Army::Rest() {m_pState->Rest();} void Army::Fiesta() {m_pState->Fiesta();} 其實也沒什么了。很容易的理解的。不知道漢武時代有沒有過閱兵儀式,如果有,那就會又多一個狀態(tài),想想我們該如何解決?挺簡單了,為State添加一個新的子類,并為它提供一個閱兵的啟動方法,當(dāng)然相應(yīng)的子類也需要添加。相應(yīng)的Army類中也需要添加該方法。這樣做,我們只是擴展了原有類的方法,而不會去改動它原有的功能。這樣就可以避免給原有功能帶來bug了。 再看看該類的調(diào)用: int main(int argc, char* argv[]) { Army army; army.Advance(); army.Assault(); army.Rest(); army.Fiesta(); army.Assault(); army.Assault(); army.Rest(); army.Fiesta(); system("pause"); return 0; } 創(chuàng)建對象后,我們可以直接調(diào)用它的函數(shù)來實現(xiàn)狀態(tài)的轉(zhuǎn)換了。 好了,狀態(tài)模式,我們先講到這里了?;叵胍幌律匣氐牟呗阅J剑遣皇怯X得很象?在《Head First Design Model》中,該模式開篇的扉頁上畫的是一幅煽情的母親流淚的圖片,說她眼瞅著自己的孩子分離,此處的兩個孩子就是策略模式和狀態(tài)模式。我們可以這兩個模式的原理都是將一個類中的屬性提煉成一個接口,再通過該接口實現(xiàn)對其子類的調(diào)用而完成所屬類的功能。它們的不同是狀態(tài)模式的接口的變化是在接口子類中完成的,而策略模式是通過所屬類來完成的。差別只是這一點,具體工作中使用哪個模式,那就的具體問題具體分析了。你只需記住我們紅體標(biāo)記的規(guī)則,就可以以不變應(yīng)萬變了。 參考書目: 1, 設(shè)計模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ)(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機械工業(yè)出版社 2, Head First Design Patterns(影印版)Freeman等著 東南大學(xué)出版社 3, 道法自然——面向?qū)ο髮嵺`指南 王詠武 王詠剛著 電子工業(yè)出版社 4, 史記 網(wǎng)上找到的電子檔 |
|