詳解 state 狀態模式及在 C++ 設計模式程式設計中的使用例項

發表於2016-12-07

每個人、事物在不同的狀態下會有不同表現(動作),而一個狀態又會在不同的表現下轉移到下一個不同的狀態(State)。最簡單的一個生活中的例子就是:地鐵入口處,如果你放入正確的地鐵票,門就會開啟讓你通過。在出口處也是驗票,如果正確你就可以 ok,否則就不讓你通過(如果你動作野蠻,或許會有報警(Alarm),:))。

有限狀態自動機(FSM)也是一個典型的狀態不同,對輸入有不同的響應(狀態轉移)。

通常我們在實現這類系統會使用到很多的 Switch/Case 語句,Case 某種狀態,發生什麼動作,Case 另外一種狀態,則發生另外一種狀態。但是這種實現方式至少有以下兩個問題:
當狀態數目不是很多的時候,Switch/Case 可能可以搞定。但是當狀態數目很多的時候(實際系統中也正是如此),維護一大組的 Switch/Case 語句將是一件異常困難並且容易出錯的事情。
狀態邏輯和動作實現沒有分離。在很多的系統實現中,動作的實現程式碼直接寫在狀態的邏輯當中。這帶來的後果就是系統的擴充套件性和維護得不到保證。

狀態模式就是被用來解決上面列出的兩個問題的,在狀態模式中我們將狀態邏輯和動作實現進行分離。當一個操作中要維護大量的 case 分支語句,並且這些分支依賴於物件的狀態。狀態模式將每一個分支都封裝到獨立的類中。

狀態模式典型的結構圖為:

201639173716593.jpg (558×259)

狀態模式的實現
程式碼片斷 1:State.h

程式碼片斷 2:State.cpp

程式碼片斷 3:Context.h

程式碼片斷 4:Context.cpp

程式碼片斷 5:main.cpp

程式碼說明:狀態模式在實現中,有兩個關鍵點:

1.將狀態宣告為 Context 的友元類(friend class),其作用是讓狀態模式訪問 Context的 protected 介面 ChangeSate()。
狀態及其子類中的操作都將 Context*傳入作為引數,其主要目的是狀態類可以通過這個指標呼叫 Context 中的方法(在本示例程式碼中沒有體現)。這也是狀態模式和 Strategy模式的最大區別所在。

2.執行了示例程式碼後可以獲得以下的結果:連續 3 次呼叫了 Context 的 OprationInterface()因為每次呼叫後狀態都會改變(A-B-A),因此該動作隨著 Context 的狀態的轉變而獲得了不同的結果。

關於State模式的一些需要注意的地方

這個模式使得軟體可以在不同的state下面呈現出完全不同的特徵

  • 不同的theme使得相同的元素呈現出不同的特點
  • 不同的state下面相同的操作產生不同的效果
  • 不同的狀態對相同的資訊產生不同的處理

這個模式使得操作的state邏輯更加的清楚,省去了無數的state判斷,而state的擴充套件性和可維護性和執行效率也大幅度的上升。關於state,有如下幾點要注意的地方:

1.所有的state應該被一個類(State Manager Class)管理:

state之間的跳轉和轉換是非常複雜的,有時一些state可能要跳轉的目標state有幾十個,這個時候我們需要一個管理類(State Manager )來統一的管理這些state的切換,例如目標state的初始化和申請跳轉state的結束處理,以及一些state間共享資料的儲存和處理。與其稱這個Manager 為管理類,不如說是一箇中間類,它實現了state之間的解隅,使得各個state之間不比知道target state的具體資訊,而只要向Manager申請跳轉就可以了。使得各個state的模組化更好,更加的靈活

2.所有的state都應該從一個state基類繼承:

既然state要教給一個manager來管理,那麼自然的,這些state都應該從一個父類繼承下來,這樣manager並不需要知道很多子類的資訊,一個最單純的manager只要只要管理一個這樣的基類的指標就可以了。另外,我們還可以統一的把state的一些共有的屬性放在這裡

3.state應該實現為一個singleton:

state並不需要總是被申請,這樣可能會造成管理上的混亂,state資源的申請也不應該可以任意進行,事實上,state的申請許可權應該只有 Manager才有,並且有且只有一次。在這樣的情況下,state的建構函式似乎應該被宣告為protected or private ,而Manager應該被宣告為state的友元,但是友元被看成是破壞類的封裝性的一種做法,這一點上,我很矛盾,所以在這一條上我只能採取一種漠視的態度。

4.應該做一個state麼?這是一個問題:

state可以說是if-else的一種替代品,極端的情況下面state可以讓你的程式中if-else程式塊消失得無影無蹤,但是,這並不是銀彈。state對於狀態可預知的情況下非常有效,但是對於state不可預知,或者相似的state數量太多。過多的state會造成class的粒度過細,程式反而不簡潔。在這樣的情況下,你應該考慮使用if-else程式塊來替代state。

例如:

有這樣的一個程式,它可以生成任意形狀的多邊形,而多邊形的各個節點是可以移動的,問題就來了。

我並不知道使用者將要使用多少個節點的多邊形,因此我無法的建立那麼多相應的state來使得這樣一個程式正常工作。state大多數都是確定的,對於不確定的,state似乎無能為力,例如此例

一種解決方法是我利用Manager傳遞給state一個state引數,讓state有機會知道使用者的操作意圖,在這個例子裡面是讓state知道使用者打算操作某一個節點,而state根據這個state引數來處理使用者的操作,比如說,state得到的是使用者操作的某一個點的index ,而state只要寫

points[index].moveTo(points[index].getX()+offset_x , points[index].getY()+offset_y);

就可以,從而避免了state過多出現的問題。

相關文章