Python狀態(tài)模式

標(biāo)簽: python 設(shè)計(jì)模式


引子

狀態(tài)模式確實(shí)很好玩,我是說(shuō)書上的例子確實(shí)很好玩,我對(duì)著電腦玩了好長(zhǎng)時(shí)間,但是想說(shuō)清楚還真不太容易,先從容易的開始吧

糖果機(jī)

糖果機(jī)操作的流程如下所示,這張圖也叫狀態(tài)圖,它顯示了糖果機(jī)的工作流程及狀態(tài)流程
狀態(tài)圖1

狀態(tài)圖.png-37kB
狀態(tài)圖.png-37kB

其中一共有四個(gè)狀態(tài)

  • 售出糖果
  • 糖果售罄
  • 有25分錢
  • 沒有25分錢
    操作糖果機(jī)會(huì)設(shè)計(jì)四個(gè)動(dòng)作
  • 投入25分錢
  • 退回25分錢
  • 轉(zhuǎn)動(dòng)曲柄
  • 發(fā)放糖果 這個(gè)動(dòng)作是糖果機(jī)內(nèi)部的動(dòng)作,機(jī)器自己調(diào)用

一開始...

創(chuàng)建一個(gè)糖果機(jī)的類,包含了四種狀態(tài)

class GumballMachine:
    def __init__(self, count):
        self.SOLD_OUT = 0   #糖果售罄狀態(tài)
        self.NO_QUARTER = 1 #沒有25分錢狀態(tài)
        self.HAS_QUARTER = 2    #有25分錢狀態(tài)
        self.SOLD = 3   #售出糖果狀態(tài)
        self.state = self.SOLD_OUT  #初始狀態(tài)為`沒有25分錢狀態(tài)`
        self.count = count  #設(shè)置一個(gè)糖果數(shù)量變量,它為0時(shí)就是糖果售罄的狀態(tài)
        if self.count > 0:  #此處如果糖果數(shù)大于0,則狀態(tài)為初始狀態(tài)
            self.state = self.NO_QUARTER

接下來(lái)該怎么辦呢?
按照上圖,狀態(tài)與狀態(tài)之間是通過(guò)動(dòng)作進(jìn)行連接的,可以對(duì)每一個(gè)動(dòng)作創(chuàng)建一個(gè)對(duì)應(yīng)的方法,這些方法利用條件語(yǔ)句來(lái)決定在四個(gè)狀態(tài)下的恰當(dāng)行為
例如投入25分錢和這個(gè)動(dòng)作在四種狀態(tài)下糖果機(jī)的反應(yīng)如下圖
狀態(tài)圖2

狀態(tài)圖2.png-42.4kB
狀態(tài)圖2.png-42.4kB

動(dòng)作代表當(dāng)前執(zhí)行的動(dòng)作是投入25分錢
如果糖果機(jī)當(dāng)前狀態(tài)是沒有25分錢,則糖果機(jī)行為是顯示你投入了25分錢,之后狀態(tài)要切換到有25分錢
如果糖果機(jī)當(dāng)前狀態(tài)是有25分錢,則糖果機(jī)行為是顯示投多了
如果糖果機(jī)當(dāng)前狀態(tài)是糖果售罄,則糖果機(jī)行為是顯示售光了,不能投了
如果糖果機(jī)當(dāng)前狀態(tài)是售出糖果,則糖果機(jī)行為是顯示投太快了,稍等,因?yàn)槭鄢鎏枪?,糖果機(jī)要恢復(fù)到初始狀態(tài)沒有25分錢
剩下的動(dòng)作和這個(gè)沒有什么區(qū)別,每個(gè)動(dòng)作都有一個(gè)狀態(tài)轉(zhuǎn)換的步驟
代碼

def insertQuarter(self):    #投入25分錢動(dòng)作
        if self.state == self.HAS_QUARTER:
            print("You cannot insert another quarter")
        elif self.state == self.NO_QUARTER:
            self.state = self.HAS_QUARTER
            print("You insert a quarter")
        elif self.state == self.SOLD_OUT:
            print("You can't insert a quarter, the machine is sold out")
        elif self.state == self.SOLD:
            print("Please wait, we're already giving you a gumball")

    def ejectQuarter(self):     #退回25分錢動(dòng)作
        if self.state == self.HAS_QUARTER:
            print("Quarter returned")
            self.state = self.NO_QUARTER
        elif self.state == self.NO_QUARTER:
            print("You haven't inserted a quarter")
        elif self.state == self.SOLD:
            print("Sorry, you already turned the crank")
        elif self.state == self.SOLD_OUT:
            print("You can't eject, you haven't inserted a quarter yet")

    def turnCrank(self):    #轉(zhuǎn)動(dòng)曲柄動(dòng)作
        if self.state == self.SOLD:
            print("Turning twice doesn't get you another gumball!")
        elif self.state == self.NO_QUARTER:
            print("You turned, but there's no quarter")
        elif self.state == self.SOLD_OUT:
            print("You turned, but there's no gumball")
        elif self.state == self.HAS_QUARTER:
            print("You turned....")
            self.state = self.SOLD
            self.dispense()     #切換到發(fā)放糖果這個(gè)內(nèi)部動(dòng)作上

    def dispense(self):     #發(fā)放糖果動(dòng)作
        if self.state == self.SOLD:
            print("A gumball comes rolling out the slot")
            self.count = self.count - 1     #發(fā)放一次糖果,糖果數(shù)量要減1
            if self.count == 0:    #糖果數(shù)量為0了,切換到糖果售罄的狀態(tài)
                print("Oops, out of gumballs")
                self.state = self.SOLD_OUT
            else:
                self.state = self.NO_QUARTER
        elif self.state == self.NO_QUARTER:
            print("You need to pay first")
        elif self.state == self.SOLD_OUT:
            print("No gumball dispense")
        elif self.state == self.HAS_QUARTER:
            print("No gumball dispense")

玩一玩....
可以增加兩個(gè)方法,玩的時(shí)候?qū)崟r(shí)查看當(dāng)前狀態(tài)和糖果數(shù)量

def getCount(self):
        print(self.count)
def getState(self):
        print(self.state)

按照下面方法玩

def main():
    gumballMachine = GumballMachine(2)
    gumballMachine.getCount()
    gumballMachine.getState()
    print("=====================================================")
    gumballMachine.insertQuarter()
    gumballMachine.getState()
    gumballMachine.ejectQuarter()
    gumballMachine.ejectQuarter()
    gumballMachine.insertQuarter()
    gumballMachine.getState()
    gumballMachine.turnCrank()
    gumballMachine.getState()
    gumballMachine.getCount()
    gumballMachine.insertQuarter()
    gumballMachine.turnCrank()
    gumballMachine.getState()
    print("=====================================================")
    gumballMachine.turnCrank()

返回的結(jié)果

2   #兩個(gè)糖果
1   #沒有25分錢狀態(tài)
=====================================================
You insert a quarter
2   #有25分錢狀態(tài)
Quarter returned
You haven't inserted a quarter
You insert a quarter
2   #有25分錢狀態(tài)
You turned....
A gumball comes rolling out the slot
1   #沒有25分錢狀態(tài)
1   #一個(gè)糖果
You insert a quarter
You turned....
A gumball comes rolling out the slot
Oops, out of gumballs
0   #糖果售罄狀態(tài)
=====================================================
You turned, but there's no gumball

這個(gè)時(shí)候如果又來(lái)了一個(gè)狀態(tài)怎么辦...
按照狀態(tài)2圖,需要增加一個(gè)新的狀態(tài),之后在每個(gè)動(dòng)作里面增加針對(duì)這個(gè)狀態(tài)的行為,好像違反了好多設(shè)計(jì)原則。

新的方法

狀態(tài)是變化的量,將變化封裝起來(lái),將動(dòng)作行為放到狀態(tài)里,這樣每個(gè)狀態(tài)只要實(shí)現(xiàn)它自己的那套行為。
為每個(gè)狀態(tài)創(chuàng)建狀態(tài)類,這些類負(fù)責(zé)在對(duì)應(yīng)的動(dòng)作下糖果機(jī)的行為

將動(dòng)作和行為委托給狀態(tài)類

狀態(tài)圖3

狀態(tài)圖3.png-50.9kB
狀態(tài)圖3.png-50.9kB

之前的狀態(tài)圖2現(xiàn)在要變成這樣了,用狀態(tài)包裹所有的動(dòng)作及對(duì)應(yīng)的行為
有25分錢狀態(tài)下,每一個(gè)動(dòng)作對(duì)應(yīng)不同的行為,在執(zhí)行退回25錢動(dòng)作后,狀態(tài)切換到沒有25分錢狀態(tài),在執(zhí)行轉(zhuǎn)動(dòng)曲柄動(dòng)作后,狀態(tài)切換到沒有25分錢狀態(tài)。
代碼
先看狀態(tài)圖3的代碼實(shí)現(xiàn)

#有25分錢狀態(tài)
class HasQuarterState(object):
    def __init__(self, gumballMachine): #傳入糖果機(jī)的實(shí)例
        self.gumballMachine = gumballMachine
    def insertQuarter(self):    #投入25分錢動(dòng)作
        print("You cannot insert another quarter")
    def ejectQuarter(self):     #退出25分錢動(dòng)作
        print("Quarter returned")
        self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())   #之后糖果機(jī)的狀
        #態(tài)切換到?jīng)]有25分錢狀態(tài)
    def turnCrank(self):    #轉(zhuǎn)動(dòng)曲柄動(dòng)作
        print("You turned....")
        self.gumballMachine.setState(self.gumballMachine.getSoldState())    #之后糖果機(jī)的狀
        #態(tài)切換到售出糖果狀態(tài)
    def dispense(self):     #發(fā)放糖果動(dòng)作,這是個(gè)內(nèi)部動(dòng)作,此處實(shí)現(xiàn)沒有作用
        print("No gumball dispense")

其他狀態(tài)的代碼也是類似

#糖果售罄狀態(tài)
class SoldOutState(object):
    def __init__(self, gumballMachine):
        self.gumballMachine = gumballMachine
    def insertQuarter(self):
        print("You can't insert a quarter, the machine is sold out")
    def ejectQuarter(self):
        print("You can't eject, you haven't inserted a quarter yet")
    def turnCrank(self):
        print("You turned, but there's no gumball")
    def dispense(self):
        print("No gumball dispense")
#沒有25分錢狀態(tài)
class NoQuarterState(object):
    def __init__(self, gumballMachine):
        self.gumballMachine = gumballMachine
    def insertQuarter(self):
        print("You inserted a quarter")
        self.gumballMachine.setState(self.gumballMachine.getHasQuarterState())
    def ejectQuarter(self):
        print("You haven't inserted a quarter")
    def turnCrank(self):
        print("You turned, but there's no quarter")
    def dispense(self):
        print("You need to pay first")
#售出糖果狀態(tài)        
class SoldState(object):
    def __init__(self, gumballMachine):
        self.gumballMachine = gumballMachine
    def insertQuarter(self):
        print("Please wait, we're already giving you a gumball")
    def ejectQuarter(self):
        print("Sorry, you already turned the crank")
    def turnCrank(self):
        print("Turning twice doesn't get you another gumball!")
    def dispense(self):
        self.gumballMachine.releaseBall()
        if self.gumballMachine.getCount()>0:
            self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())
        else:
            print("Oops, out of gumballs")
            self.gumballMachine.setState(self.gumballMachine.getSoldOutState())

看看糖果機(jī)的實(shí)現(xiàn)

#糖果機(jī)類
class GumballMachine:
    def __init__(self, numberGumballs):
        self.count = numberGumballs
#=========創(chuàng)建每一個(gè)狀態(tài)的狀態(tài)實(shí)例====================#
        self.soldOutState = SoldOutState(self)
        self.noQuarterState = NoQuarterState(self)
        self.hasQuarterState = HasQuarterState(self)
        self.soldState = SoldState(self)
#=========end=========================================#
        if self.count > 0:
            self.state = self.noQuarterState
#============每個(gè)狀態(tài)的get方法和set方法===============#
    def getSoldOutState(self):
        return self.soldOutState
    def getNoQuarterState(self):
        return self.noQuarterState
    def getHasQuarterState(self):
        return self.hasQuarterState
    def getSoldState(self):
        return self.soldState
    def setState(self, state):
        self.state = state
#=========end=========================================#
#============將方法委托給當(dāng)前的狀態(tài)===================#
    def insertQuarter(self):
        self.state.insertQuarter()
    def ejectQuarter(self):
        self.state.ejectQuarter()
    def turnCrank(self):
        if self.state == self.hasQuarterState:
            self.state.turnCrank()
            self.state.dispense()
        else:
            self.state.turnCrank()
#=========end=========================================#
    def releaseBall(self):
        print("A gumball comes rolling out the slot...")
        if self.count != 0:
            self.count -= 1
#============檢查狀態(tài)和糖果數(shù)量的方法=================#
    def getState(self):
        print(self.state)
    def getCount(self):
        return self.count

還是用之前得測(cè)試代碼,看看返回

2   #糖果數(shù)量
<__main__.NoQuarterState object at 0x01D6BC90>      #當(dāng)前狀態(tài)
=====================================================
You inserted a quarter      #行為
<__main__.HasQuarterState object at 0x01D6BCB0>     #當(dāng)前狀態(tài)
Quarter returned        #行為
You haven't inserted a quarter      #行為
You inserted a quarter      #行為
<__main__.HasQuarterState object at 0x01D6BCB0>     #當(dāng)前狀態(tài)
You turned....      #行為
A gumball comes rolling out the slot...     #行為
<__main__.NoQuarterState object at 0x01D6BC90>      #當(dāng)前狀態(tài)
1       #糖果數(shù)量
You inserted a quarter      #行為
You turned....      #行為
A gumball comes rolling out the slot...     #行為
Oops, out of gumballs       #行為
<__main__.SoldOutState object at 0x01D6BC70>        #當(dāng)前狀態(tài)
=====================================================
You turned, but there's no gumball      #行為

看看執(zhí)行圖

執(zhí)行圖.png-59.2kB
執(zhí)行圖.png-59.2kB

初始狀態(tài)是沒有25分錢,執(zhí)行投入25分錢動(dòng)作
糖果機(jī)切換狀態(tài)到第二步狀態(tài)有25分錢,之后執(zhí)行轉(zhuǎn)動(dòng)曲柄動(dòng)作
糖果機(jī)切換狀態(tài)到第三步狀態(tài)售出糖果,之后執(zhí)行發(fā)放糖果動(dòng)作
如果糖果數(shù)目為0,則糖果機(jī)切換狀態(tài)到第四步狀態(tài)糖果售罄
糖果機(jī)動(dòng)作和行為都委托給了每種狀態(tài),狀態(tài)一變,糖果機(jī)的行為就是此種狀態(tài)下的動(dòng)作產(chǎn)生的行為了,這樣一來(lái),如果增加了一種狀態(tài),只要單獨(dú)實(shí)現(xiàn)這個(gè)狀態(tài)下糖果機(jī)所有的行為就OK了。

再來(lái)一個(gè)狀態(tài)

增加一個(gè)游戲狀態(tài),轉(zhuǎn)曲柄獲取糖果的時(shí)候,有10%的機(jī)會(huì)能成為大贏家,獲得附贈(zèng)的一粒糖果,這個(gè)怎么搞?
增加一個(gè)狀態(tài)winnerState

class WinnerState(object):
    def __init__(self, gumballMachine):
        self.gumballMachine = gumballMachine
    def insertQuarter(self):
        print("Please wait, we're already giving you a gumball")
    def ejectQuarter(self):
        print("Sorry, you already turned the crank")
    def turnCrank(self):
        print("Turning twice doesn't get you another gumball!")
    # 你贏了,如果糖果沒了,那就算了,只能白贏了;
    def dispense(self):
        print("You are winner! You get 2 gumball for youe quarter")
        if self.gumballMachine.getCount()==0:
            self.gumballMachine.setState(self.gumballMachine.getSoldOutState())
        else:
            self.gumballMachine.releaseBall()
            if self.gumballMachine.getCount()>0:
                self.gumballMachine.releaseBall()
                self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())
            else:
                print("Oops, out of gumballs")
                self.gumballMachine.setState(self.gumballMachine.getSoldOutState())

隨機(jī)數(shù)怎么整,random.randint這個(gè)就能實(shí)現(xiàn),但是這個(gè)動(dòng)作要增加在哪里呢,哪個(gè)狀態(tài)下轉(zhuǎn)動(dòng)曲柄可以獲得糖果,是有25分錢這個(gè)狀態(tài),只要將這個(gè)狀態(tài)下的轉(zhuǎn)曲柄動(dòng)作稍微改動(dòng)一下就OK了

def turnCrank(self):
        print("You turned....")
        #產(chǎn)生隨機(jī)數(shù)
        self.winner = random.randint(1, 100)
        #這個(gè)數(shù)為1你就贏了
        if self.winner == 1:
            self.gumballMachine.setState(self.gumballMachine.getWinnerState())
        else:
            self.gumballMachine.setState(self.gumballMachine.getSoldState())

開始玩吧

def main():
    gumballMachine = GumballMachine(100)
    print(gumballMachine.getCount())
    for i in range(5):
        print("======================{0}====================".format(i+1))
        gumballMachine.insertQuarter()
        gumballMachine.turnCrank()
    print(gumballMachine.getCount())

5次幾率好像太小,應(yīng)該中不了

======================1====================
You inserted a quarter
You turned....
You are winner! You get 2 gumball for youe quarter
A gumball comes rolling out the slot...
A gumball comes rolling out the slot...
======================2====================
You inserted a quarter
You turned....
A gumball comes rolling out the slot...
======================3====================
You inserted a quarter
You turned....
A gumball comes rolling out the slot...
======================4====================
You inserted a quarter
You turned....
A gumball comes rolling out the slot...
======================5====================
You inserted a quarter
You turned....
A gumball comes rolling out the slot...
94

靠?。。。?!第一次就中了

定義

狀態(tài)模式允許對(duì)象在內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來(lái)好像修改了它的類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,237評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,957評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,248評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,356評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,081評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,485評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,534評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,720評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,263評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,025評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,204評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,787評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,461評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,874評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,105評(píng)論 1 289
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,945評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,205評(píng)論 2 375

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