Golang設(shè)計(jì)模式——狀態(tài)模式

狀態(tài)模式及其結(jié)構(gòu)

狀態(tài)模式(State):當(dāng)一個(gè)對(duì)象的內(nèi)部狀態(tài)發(fā)生改變時(shí)阀蒂,會(huì)導(dǎo)致其行為的改變何鸡,對(duì)象看起來似乎修改了它的類肾扰。其別名為狀態(tài)對(duì)象(Objects for States),狀態(tài)模式是一種對(duì)象行為型模式抄伍。狀態(tài)模式用于解決系統(tǒng)中復(fù)雜對(duì)象的狀態(tài)轉(zhuǎn)換以及不同狀態(tài)下行為的封裝問題熏挎。當(dāng)系統(tǒng)中某個(gè)對(duì)象存在多個(gè)狀態(tài),這些狀態(tài)之間可以進(jìn)行轉(zhuǎn)換前痘,而且對(duì)象在不同狀態(tài)下行為不相同時(shí)可以使用狀態(tài)模式凛捏。

模式的結(jié)構(gòu)

UML

狀態(tài)模式.png

在狀態(tài)模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Context(環(huán)境類):環(huán)境類又稱為上下文類,它是擁有多種狀態(tài)的對(duì)象芹缔。由于環(huán)境類的狀態(tài)存在多樣性且在不同狀態(tài)下對(duì)象的行為有所不同坯癣,因此將狀態(tài)獨(dú)立出去形成單獨(dú)的狀態(tài)類。在環(huán)境類中維護(hù)一個(gè)抽象狀態(tài)類State的實(shí)例最欠,這個(gè)實(shí)例定義當(dāng)前狀態(tài)示罗,在具體實(shí)現(xiàn)時(shí),它是一個(gè)State子類的對(duì)象芝硬。
  • State(抽象狀態(tài)類):它用于定義一個(gè)接口以封裝與環(huán)境類的一個(gè)特定狀態(tài)相關(guān)的行為蚜点,在抽象狀態(tài)類中聲明了各種不同狀態(tài)對(duì)應(yīng)的方法,而在其子類中實(shí)現(xiàn)類這些方法拌阴,由于不同狀態(tài)下對(duì)象的行為可能不同绍绘,因此在不同子類中方法的實(shí)現(xiàn)可能存在不同,相同的方法可以寫在抽象狀態(tài)類中迟赃。
  • ConcreteState(具體狀態(tài)類):它是抽象狀態(tài)類的子類陪拘,每一個(gè)子類實(shí)現(xiàn)一個(gè)與環(huán)境類的一個(gè)狀態(tài)相關(guān)的行為,每一個(gè)具體狀態(tài)類對(duì)應(yīng)環(huán)境的一個(gè)具體狀態(tài)纤壁,不同的具體狀態(tài)類其行為有所不同左刽。

代碼示例

當(dāng)今社會(huì),論壇貼吧很多酌媒,我們也會(huì)加入感興趣的論壇欠痴,偶爾進(jìn)行發(fā)言,但有時(shí)卻會(huì)發(fā)現(xiàn)不能發(fā)帖了馍佑,原來是昨天的某個(gè)帖子引發(fā)了口水戰(zhàn)斋否,被舉報(bào)了。這里就用論壇發(fā)帖為例拭荤,簡(jiǎn)單用代碼描述一下:

帖子代碼示例圖.png

假設(shè)有三種狀態(tài)茵臭,normal(正常),restricted(受限)舅世,closed(封號(hào))旦委,判斷依據(jù)是一個(gè)健康值(這里只是假設(shè))奇徒。

2.1不用狀態(tài)模式

/* @Time    : 2018/8/10 下午3:16
 **@Author  : panda
 **@Email   : codepanda_li@163.com
 **@File    : account.go
 **@Software: GoLand
 */
package account

import "fmt"

type AccountState int

const (
   NORMAL     AccountState = iota //正常0
   RESTRICTED                     //受限
   CLOSED                         //封號(hào)
)

type Account struct {
   State       AccountState
   HealthValue int
}

func NewAccount(health int) *Account {
   a := &Account{
      HealthValue: health,
   }
   a.changeState()
   return a
}

///看帖
func (a *Account) View() {
   if a.State == NORMAL || a.State == RESTRICTED {
      fmt.Println("正常看帖")
   } else if a.State == CLOSED {
      fmt.Println("賬號(hào)被封缨硝,無法看帖")
   }

}

///評(píng)論
func (a *Account) Comment() {
   if a.State == NORMAL || a.State == RESTRICTED {
      fmt.Println("正常評(píng)論")
   } else if a.State == CLOSED {
      fmt.Println("抱歉摩钙,你的健康值小于-10,不能評(píng)論")
   }

}

///發(fā)帖
func (a *Account) Post() {
   if a.State == NORMAL {
      fmt.Println("正常發(fā)帖")
   } else if a.State == RESTRICTED || a.State == CLOSED {
      fmt.Println("抱歉查辩,你的健康值小于0胖笛,不能發(fā)帖")
   }
}

func (a *Account) changeState() {
   if a.HealthValue <= -10 {
      a.State = CLOSED
   } else if a.HealthValue > -10 && a.HealthValue <= 0 {
      a.State = RESTRICTED
   } else if a.HealthValue > 0 {
      a.State = NORMAL
   }
}

///給賬戶設(shè)定健康值
func (a *Account) SetHealth(value int) {
   a.HealthValue = value
   a.changeState()
}

上面的代碼很簡(jiǎn)單,能夠?qū)崿F(xiàn)需要的功能宜岛,但是卻有幾個(gè)問題:

  • 看帖和發(fā)帖方法中都包含狀態(tài)判斷語(yǔ)句长踊,以判斷在該狀態(tài)下是否具有該方法以及在特定狀態(tài)下該方法如何實(shí)現(xiàn),導(dǎo)致代碼非常冗長(zhǎng)萍倡,可維護(hù)性較差身弊;
  • 系統(tǒng)擴(kuò)展性較差,如果需要增加一種新的狀態(tài)列敲,如hot狀態(tài)(活躍用戶阱佛,該狀態(tài)用戶發(fā)帖積分增加更多),需要對(duì)原有代碼進(jìn)行大量修改戴而,擴(kuò)展起來非常麻煩凑术。

2.2使用狀態(tài)模式

狀態(tài)模式可以在一定程度上解決上述問題,在狀態(tài)模式中將對(duì)象在每一個(gè)狀態(tài)下的行為和狀態(tài)轉(zhuǎn)移語(yǔ)句封裝在一個(gè)個(gè)狀態(tài)類中填硕,通過這些狀態(tài)類來分散冗長(zhǎng)的條件轉(zhuǎn)移語(yǔ)句麦萤,讓系統(tǒng)具有更好的靈活性和可擴(kuò)展性。

/* @Time    : 2018/8/10 下午3:37
 **@Author  : panda
 **@Email   : codepanda_li@163.com
 **@File    : account.go
 **@Software: GoLand
 */
package saccount

import "fmt"

type Account struct {
   State       ActionState
   HealthValue int
}
func NewAccount(health int) *Account {
   a := &Account{
      HealthValue: health,
   }
   a.changeState()
   return a
}

func (a *Account)View()  {
   a.State.View()
}

func (a *Account)Comment()  {
   a.State.Comment()
}
func (a *Account)Post()  {
   a.State.Post()
}
type ActionState interface {
   View()
   Comment()
   Post()
}

type CloseState struct {

}

func (c *CloseState)View()  {
   fmt.Println("賬號(hào)被封扁眯,無法看帖")
}

func (c *CloseState)Comment()  {
   fmt.Println("抱歉,你的健康值小于-10翅帜,不能評(píng)論")
}
func (c *CloseState)Post()  {
   fmt.Println("抱歉姻檀,你的健康值小于0,不能發(fā)帖")
}

type RestrictedState struct {

}
func (r *RestrictedState)View()  {
   fmt.Println("正忱缘危看帖")
}

func (r *RestrictedState)Comment()  {
   fmt.Println("正常評(píng)論")
}
func (r *RestrictedState)Post()  {
   fmt.Println("抱歉绣版,你的健康值小于0,不能發(fā)帖")
}

type NormalState struct {

}
func (n *NormalState)View()  {
   fmt.Println("正臣叽看帖")
}

func (n *NormalState)Comment()  {
   fmt.Println("正常評(píng)論")
}
func (n *NormalState)Post()  {
   fmt.Println("正常發(fā)帖")
}
func (a *Account) changeState() {
   if a.HealthValue <= -10 {
      a.State = &CloseState{}
   } else if a.HealthValue > -10 && a.HealthValue <= 0 {
      a.State = &RestrictedState{}
   } else if a.HealthValue > 0 {
      a.State = &NormalState{}
   }
}

///給賬戶設(shè)定健康值
func (a *Account) SetHealth(value int) {
   a.HealthValue = value
   a.changeState()
}

優(yōu)點(diǎn)和缺點(diǎn)

優(yōu)點(diǎn)

狀態(tài)模式的主要優(yōu)點(diǎn)如下:

  • 封裝了狀態(tài)的轉(zhuǎn)換規(guī)則杂抽,在狀態(tài)模式中可以將狀態(tài)的轉(zhuǎn)換代碼封裝在環(huán)境類或者具體狀態(tài)類中,可以對(duì)狀態(tài)轉(zhuǎn)換代碼進(jìn)行集中管理韩脏,而不是分散在一個(gè)個(gè)業(yè)務(wù)方法中缩麸。
  • 將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,只需要注入一個(gè)不同的狀態(tài)對(duì)象即可使環(huán)境對(duì)象擁有不同的行為赡矢。
  • 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體杭朱,而不是提供一個(gè)巨大的條件語(yǔ)句塊阅仔,狀態(tài)模式可以避免使用龐大的條件語(yǔ)句來將業(yè)務(wù)方法和狀態(tài)轉(zhuǎn)換代碼交織在一起。
  • 可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象弧械,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)八酒。

缺點(diǎn)

狀態(tài)模式的主要缺點(diǎn)如下:

  • 狀態(tài)模式的使用必然會(huì)增加系統(tǒng)中類和對(duì)象的個(gè)數(shù),導(dǎo)致系統(tǒng)運(yùn)行開銷增大刃唐。
  • 狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜羞迷,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂,增加系統(tǒng)設(shè)計(jì)的難度画饥。
  • 狀態(tài)模式對(duì)“開閉原則”的支持并不太好衔瓮,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法轉(zhuǎn)換到新增狀態(tài)荒澡;而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼报辱。

適用環(huán)境

在以下情況下可以使用狀態(tài)模式:

  • 對(duì)象的行為依賴于它的狀態(tài)(屬性)并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。
  • 代碼中包含大量與對(duì)象狀態(tài)有關(guān)的條件語(yǔ)句单山,這些條件語(yǔ)句的出現(xiàn)碍现,會(huì)導(dǎo)致代碼的可維護(hù)性和靈活性變差,不能方便地增加和刪除狀態(tài)米奸,使客戶類與類庫(kù)之間的耦合增強(qiáng)昼接。

模式應(yīng)用

狀態(tài)模式在工作流或游戲等類型的軟件中得以廣泛使用,甚至可以用于這些系統(tǒng)的核心功能設(shè)計(jì)悴晰,如在政府OA辦公系統(tǒng)中慢睡,一個(gè)批文的狀態(tài)有多種:尚未辦理;正在辦理铡溪;正在批示漂辐;正在審核;已經(jīng)完成等各種狀態(tài)棕硫,而且批文狀態(tài)不同時(shí)對(duì)批文的操作也有所差異髓涯。使用狀態(tài)模式可以描述工作流對(duì)象(如批文)的狀態(tài)轉(zhuǎn)換以及不同狀態(tài)下它所具有的行為。

說明一下哈扮,這個(gè)貼子的示例是我印象中看過一個(gè)java的對(duì)狀態(tài)模式的實(shí)現(xiàn)纬纪,覺得很恰當(dāng)明了,然后自己用golang實(shí)現(xiàn)了一遍滑肉,現(xiàn)在只有g(shù)oalng示例代碼包各,忘記了那篇java的出處了。對(duì)那個(gè)java的作者表示敬意靶庙。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末问畅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子辱挥,更是在濱河造成了極大的恐慌飒责,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜂林,死亡現(xiàn)場(chǎng)離奇詭異签则,居然都是意外死亡须床,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門渐裂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豺旬,“玉大人,你說我怎么就攤上這事柒凉∽逶模” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵膝捞,是天一觀的道長(zhǎng)坦刀。 經(jīng)常有香客問我,道長(zhǎng)蔬咬,這世上最難降的妖魔是什么鲤遥? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮林艘,結(jié)果婚禮上盖奈,老公的妹妹穿的比我還像新娘。我一直安慰自己狐援,他們只是感情好钢坦,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啥酱,像睡著了一般爹凹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上镶殷,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天逛万,我揣著相機(jī)與錄音,去河邊找鬼批钠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛得封,可吹牛的內(nèi)容都是我干的埋心。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼忙上,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼拷呆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤茬斧,失蹤者是張志新(化名)和其女友劉穎腰懂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體项秉,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绣溜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了娄蔼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怖喻。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖岁诉,靈堂內(nèi)的尸體忽然破棺而出锚沸,到底是詐尸還是另有隱情,我是刑警寧澤涕癣,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布哗蜈,位于F島的核電站,受9級(jí)特大地震影響坠韩,放射性物質(zhì)發(fā)生泄漏距潘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一同眯、第九天 我趴在偏房一處隱蔽的房頂上張望绽昼。 院中可真熱鬧,春花似錦须蜗、人聲如沸硅确。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)菱农。三九已至,卻和暖如春柿估,著一層夾襖步出監(jiān)牢的瞬間循未,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工秫舌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留的妖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓足陨,卻偏偏與公主長(zhǎng)得像嫂粟,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子墨缘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容