狀態模式使用的相對較少,主要是因為會引入大量的狀態類,導致程式碼比較難維護。但是合適的場景使用狀態模式,可以把複雜的判斷邏輯簡化。
UML類圖位置:https://www。processon。com/view/link/60d29bf3e401fd49502afd25
本文程式碼連結為:https://github。com/shidawuhen/asap/blob/master/controller/design/22status。go
1。定義
1。1狀態模式
狀態模式
:當一個物件的內在狀態改變時允許改變其行為,這個物件看起來像是改變了其類。
UML
:
1。2分析
狀態機有3個組成部分:狀態(State)、事件(Event)、動作(Action)。事件觸發狀態的轉移及動作的執行。
只看定義和UML,可能比較難理解使用狀態模式有什麼好處,舉個例子就清晰了。
假設有四種狀態A、B、C、D,同時有四種觸發事件E1、E2、E3、E4,如果不使用狀態模式,寫出來的樣子是這樣的:
func E1() { if status == “A” { //狀態遷移+動作執行 } else if status == “B” { //狀態遷移+動作執行 } else if status == “C” { //狀態遷移+動作執行 } else if status == “D” { //狀態遷移+動作執行 }}func E2() { if status == “A” { //狀態遷移+動作執行 } else if status == “B” { //狀態遷移+動作執行 } else if status == “C” { //狀態遷移+動作執行 } else if status == “D” { //狀態遷移+動作執行 }}
單看虛擬碼可能覺得還好,但是細想一想,如果動作執行比較複雜,程式碼是不是就很醜了。後期如果狀態或者事件變更,如何確保每一處都進行了更改?這時候狀態模式便起作用了。
我們建立四個類,如UML中的ConcreteStateA、ConcreteStateB、ConcreteStateC、ConcreteStateD,分別代表四種狀態。每個狀態類中有4個Handle函式,分別對應4個事件。透過這種方式,將糅雜在一起的邏輯進行了拆分,程式碼看起來優雅了很多。
2。應用場景
這麼多年都沒有用過狀態模式,使用場景確實有點少。
實際業務場景中做過跨境履約單的狀態機,履約單需要經歷接單、清關中、清關成功、發貨等狀態。這種場景相對簡單,狀態只能單方向流轉、單獨的介面觸發指定狀態流轉到下一個狀態,複雜的部分在於下一個狀態可能有多個,有的可以跳過。這種情況下,使用陣列維護狀態機,比狀態模式要好。
如果狀態多、動作執行邏輯複雜,那使用狀態模式還是挺合理的,一般遊戲中使用狀態模式相對多一些。本次借用《設計模式之美》裡超級馬里奧的例子,使用超級馬里奧介紹實在是太合適了,一是因為馬里奧有多種狀態、多種觸發事件,特別適合使用狀態模式;二是超級馬里奧大家都玩過,業務情況大家都熟悉。為了幫助大家回憶,我找了馬里奧全系列變身形態https://zhuanlan。zhihu。com/p/250931383。
3。程式碼實現
馬里奧狀態有小馬里奧(Small Mario)、超級馬里奧(Super Mario)、斗篷馬里奧(Cape Mario),小馬里奧吃了蘑菇變為超級馬里奧,小馬里奧和超級馬里奧獲得斗篷變成斗篷馬里奧,超級馬里奧和斗篷馬里奧碰到怪物變成小馬里奧。
package mainimport “fmt”type Mario struct { score int64 status MarioStatus}type MarioStatus interface { Name() ObtainMushroom() ObtainCape() MeetMonster() SetMario(mario *Mario)}/** * @Author: Jason Pang * @Description: 小馬里奧 */type SmallMarioStatus struct { mario *Mario}/** * @Author: Jason Pang * @Description: 設定馬里奧 * @receiver s * @param mario */func (s *SmallMarioStatus) SetMario(mario *Mario) { s。mario = mario}func (s *SmallMarioStatus) Name() { fmt。Println(“小馬里奧”)}/** * @Author: Jason Pang * @Description: 獲得蘑菇變為超級馬里奧 * @receiver s */func (s *SmallMarioStatus) ObtainMushroom() { s。mario。status = &SuperMarioStatus{ mario: s。mario, } s。mario。score += 100}/** * @Author: Jason Pang * @Description: 獲得斗篷變為斗篷馬里奧 * @receiver s */func (s *SmallMarioStatus) ObtainCape() { s。mario。status = &CapeMarioStatus{ mario: s。mario, } s。mario。score += 200}/** * @Author: Jason Pang * @Description: 遇到怪獸減100 * @receiver s */func (s *SmallMarioStatus) MeetMonster() { s。mario。score -= 100}/** * @Author: Jason Pang * @Description: 超級馬里奧 */type SuperMarioStatus struct { mario *Mario}/** * @Author: Jason Pang * @Description: 設定馬里奧 * @receiver s * @param mario */func (s *SuperMarioStatus) SetMario(mario *Mario) { s。mario = mario}func (s *SuperMarioStatus) Name() { fmt。Println(“超級馬里奧”)}/** * @Author: Jason Pang * @Description: 獲得蘑菇無變化 * @receiver s */func (s *SuperMarioStatus) ObtainMushroom() {}/** * @Author: Jason Pang * @Description:獲得斗篷變為斗篷馬里奧 * @receiver s */func (s *SuperMarioStatus) ObtainCape() { s。mario。status = &CapeMarioStatus{ mario: s。mario, } s。mario。score += 200}/** * @Author: Jason Pang * @Description: 遇到怪獸變為小馬里奧 * @receiver s */func (s *SuperMarioStatus) MeetMonster() { s。mario。status = &SmallMarioStatus{ mario: s。mario, } s。mario。score -= 200}/** * @Author: Jason Pang * @Description: 斗篷馬里奧 */type CapeMarioStatus struct { mario *Mario}/** * @Author: Jason Pang * @Description: 設定馬里奧 * @receiver s * @param mario */func (c *CapeMarioStatus) SetMario(mario *Mario) { c。mario = mario}func (c *CapeMarioStatus) Name() { fmt。Println(“斗篷馬里奧”)}/** * @Author: Jason Pang * @Description:獲得蘑菇無變化 * @receiver c */func (c *CapeMarioStatus) ObtainMushroom() {}/** * @Author: Jason Pang * @Description: 獲得斗篷無變化 * @receiver c */func (c *CapeMarioStatus) ObtainCape() {}/** * @Author: Jason Pang * @Description: 遇到怪獸變為小馬里奧 * @receiver c */func (c *CapeMarioStatus) MeetMonster() { c。mario。status = &SmallMarioStatus{ mario: c。mario, } c。mario。score -= 200}func main() { mario := Mario{ status: &SmallMarioStatus{}, score: 0, } mario。status。SetMario(&mario) mario。status。Name() fmt。Println(“——————————-獲得蘑菇\n”) mario。status。ObtainMushroom() mario。status。Name() fmt。Println(“——————————-獲得斗篷\n”) mario。status。ObtainCape() mario。status。Name() fmt。Println(“——————————-遇到怪獸\n”) mario。status。MeetMonster() mario。status。Name()}
輸出:
➜ myproject go run main。go
小馬里奧
——————————-獲得蘑菇
超級馬里奧
——————————-獲得斗篷
斗篷馬里奧
——————————-遇到怪獸
小馬里奧
總結
仔細看上面的程式碼
對事件觸發狀態的轉移及動作的執行的改動會很簡單
可快速增加新的事件
增加新的狀態也方便,只需新增新的狀態類,少量修改已有程式碼
壞處就是類特別多,類裡的函式也會特別多,即使這些函式根本無用。不過能獲得更好的擴充套件性,還是值得的。
最後
大家如果喜歡我的文章,可以關注我的公眾號(程式設計師麻辣燙)
我的個人部落格為:https://shidawuhen。github。io/
往期文章回顧:
設計模式
招聘
思考
儲存
算法系列
讀書筆記
小工具
架構
網路
Go語言