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

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

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

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

UML

狀態(tài)模式.png

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

  • Context(環(huán)境類):環(huán)境類又稱為上下文類,它是擁有多種狀態(tài)的對象。由于環(huán)境類的狀態(tài)存在多樣性且在不同狀態(tài)下對象的行為有所不同,因此將狀態(tài)獨(dú)立出去形成單獨(dú)的狀態(tài)類。在環(huán)境類中維護(hù)一個抽象狀態(tài)類State的實(shí)例,這個實(shí)例定義當(dāng)前狀態(tài),在具體實(shí)現(xiàn)時,它是一個State子類的對象。
  • State(抽象狀態(tài)類):它用于定義一個接口以封裝與環(huán)境類的一個特定狀態(tài)相關(guān)的行為,在抽象狀態(tài)類中聲明了各種不同狀態(tài)對應(yīng)的方法,而在其子類中實(shí)現(xiàn)類這些方法,由于不同狀態(tài)下對象的行為可能不同,因此在不同子類中方法的實(shí)現(xiàn)可能存在不同,相同的方法可以寫在抽象狀態(tài)類中。
  • ConcreteState(具體狀態(tài)類):它是抽象狀態(tài)類的子類,每一個子類實(shí)現(xiàn)一個與環(huán)境類的一個狀態(tài)相關(guān)的行為,每一個具體狀態(tài)類對應(yīng)環(huán)境的一個具體狀態(tài),不同的具體狀態(tài)類其行為有所不同。

代碼示例

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

帖子代碼示例圖.png

假設(shè)有三種狀態(tài),normal(正常),restricted(受限),closed(封號),判斷依據(jù)是一個健康值(這里只是假設(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                         //封號
)

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("賬號被封,無法看帖")
   }

}

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

}

///發(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()
}

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

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

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

狀態(tài)模式可以在一定程度上解決上述問題,在狀態(tài)模式中將對象在每一個狀態(tài)下的行為和狀態(tài)轉(zhuǎn)移語句封裝在一個個狀態(tài)類中,通過這些狀態(tài)類來分散冗長的條件轉(zhuǎn)移語句,讓系統(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("賬號被封,無法看帖")
}

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

type RestrictedState struct {

}
func (r *RestrictedState)View()  {
   fmt.Println("正??刺?)
}

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

type NormalState struct {

}
func (n *NormalState)View()  {
   fmt.Println("正??刺?)
}

func (n *NormalState)Comment()  {
   fmt.Println("正常評論")
}
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)類中,可以對狀態(tài)轉(zhuǎn)換代碼進(jìn)行集中管理,而不是分散在一個個業(yè)務(wù)方法中。
  • 將所有與某個狀態(tài)有關(guān)的行為放到一個類中,只需要注入一個不同的狀態(tài)對象即可使環(huán)境對象擁有不同的行為。
  • 允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對象合成一體,而不是提供一個巨大的條件語句塊,狀態(tài)模式可以避免使用龐大的條件語句來將業(yè)務(wù)方法和狀態(tài)轉(zhuǎn)換代碼交織在一起。
  • 可以讓多個環(huán)境對象共享一個狀態(tài)對象,從而減少系統(tǒng)中對象的個數(shù)。

缺點(diǎn)

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

  • 狀態(tài)模式的使用必然會增加系統(tǒng)中類和對象的個數(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)模式對“開閉原則”的支持并不太好,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法轉(zhuǎn)換到新增狀態(tài);而且修改某個狀態(tài)類的行為也需修改對應(yīng)類的源代碼。

適用環(huán)境

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

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

模式應(yīng)用

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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容