python設計模式-狀態模式
阿新 • • 發佈:2019-01-01
問題:
有一個糖果公司需要設計一個糖果售賣機,控制流程如下圖,需要怎麼實現?
這是一個狀態圖,每個圓圈都是一種狀態。很明顯,有有25分錢
、 沒有25分錢
、 售出糖果
、 糖果售罄
四個狀態,同時也對應四個動作:投入25分錢
,退回25分錢
,轉動曲柄
和發放糖果
。
那如何從狀態圖得到真正的程式碼呢?
簡單程式碼實現如下:
#! -*- coding: utf-8 -*-
class GumballMachine:
# 找出所有狀態,並建立例項變數來持有當前狀態,然後定義狀態的值
STATE_SOLD_OUT = 0
STATE_NO_QUARTER = 1
STATE_HAS_QUARTER = 2
STATE_SOLD = 3
state = STATE_SOLD_OUT
def __init__(self, count=0):
self.count = count
if count > 0:
self.state = self.STATE_NO_QUARTER
def __str__(self):
return "Gumball machine current state: %s" % self.state
def insert_quarter(self):
# 投入25分錢
if self.state == self.STATE_HAS_QUARTER: # 如果已經投過
print("You can't insert another quarter")
elif self.state == self.STATE_NO_QUARTER: # 如果沒有投過
self.state = self.STATE_HAS_QUARTER
print("You inserted a quarter" )
elif self.state == self.STATE_SOLD_OUT: # 如果已經售罄
print("You can't insert a quarter, the machine is sold out")
elif self.state == self.STATE_SOLD: # 如果剛剛買了糖果
print("Please wait, we're already giving you a gumball")
def eject_quarter(self):
# 退回25分
if self.state == self.STATE_HAS_QUARTER:
print("Quarter returned")
self.state = self.STATE_NO_QUARTER
elif self.state == self.STATE_NO_QUARTER:
print("You haven't inserted a quarter")
elif self.state == self.STATE_SOLD:
print("Sorry, you alread turned the crank")
elif self.state == self.SOLD_OUT:
print("You can't eject, you haven't inserted")
def turn_crank(self):
# 轉動曲柄
if self.state == self.STATE_SOLD:
print("Turning twice doesn't get you another gumball")
elif self.state == self.STATE_NO_QUARTER:
print("You turned but there's no quarter")
elif self.state == self.STATE_SOLD_OUT:
print("You turned, but there are no gumballs")
elif self.state == self.STATE_HAS_QUARTER:
print("You turned...")
self.state = self.STATE_SOLD
self.dispense()
def dispense(self):
# 發放糖果
if self.state == self.STATE_SOLD:
print("A gumball comes rolling out the slot")
self.count -= 1
if self.count == 0:
self.state = self.STATE_SOLD_OUT
else:
self.state = self.STATE_NO_QUARTER
elif self.state == self.STATE_NO_QUARTER:
print("You need to pay first")
elif self.state == self.STATE_SOLD_OUT:
print("No gumball dispensed")
elif self.state == self.STATE_HAS_QUARTER:
print("No gumball dispensed")
if __name__ == "__main__":
# 以下是程式碼測試
gumball_machine = GumballMachine(5) # 裝入5 個糖果
print(gumball_machine)
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
print(gumball_machine)
gumball_machine.insert_quarter() #投入25分錢
gumball_machine.eject_quarter() # 退錢
gumball_machine.turn_crank() # 轉動曲柄
print(gumball_machine)
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
gumball_machine.eject_quarter() # 退錢
print(gumball_machine)
複製程式碼
這段程式碼有幾個問題:
- 沒有遵守開放-關閉原則
- 更像是面向過程的設計
- 狀態轉化被埋藏在條件語句中
- 未來加入新的需求,需要改動的較多,不易維護,可能會出bug
如何改進呢?
考慮封裝變化,把每個狀態的行為都放在各自的類中,每個狀態只要實現自己的動作,用加入新類的方式來實現新狀態的加入。
- 定義State 父類,在這個類中,糖果機的每個動作都有一個應對的方法
- 為機器中的每個狀態實現狀態類,這些類將負責在對應的狀態下進行機器的行為
- 擺脫舊的條件程式碼,將動作委託到狀態類
新的實現程式碼如下:
#! -*- coding: utf-8 -*-
class State:
# 定義state基類
def insert_quarter(self):
pass
def eject_quarter(self):
pass
def turn_crank(self):
pass
def dispense(self):
pass
class SoldOutState(State):
# 繼承State 類
def __init__(self, gumball_machine):
self.gumball_machine = gumball_machine
def __str__(self):
return "sold_out"
def insert_quarter(self):
print("You can't insert a quarter, the machine is sold out")
def eject_quarter(self):
print("You can't eject, you haven't inserted a quarter yet")
def turn_crank(self):
print("You turned, but ther are no gumballs")
def dispense(self):
print("No gumball dispensed")
class SoldState(State):
# 繼承State 類
def __init__(self, gumball_machine):
self.gumball_machine = gumball_machine
def __str__(self):
return "sold"
def insert_quarter(self):
print("Please wait, we're already giving you a gumball")
def eject_quarter(self):
print("Sorry, you already turned the crank")
def turn_crank(self):
print("Turning twice doesn't get you another gumball")
def dispense(self):
self.gumball_machine.release_ball()
if gumball_machine.count > 0:
self.gumball_machine.state = self.gumball_machine.no_quarter_state
else:
print("Oops, out of gumballs!")
self.gumball_machine.state = self.gumball_machine.soldout_state
class NoQuarterState(State):
# 繼承State 類
def __init__(self, gumball_machine):
self.gumball_machine = gumball_machine
def __str__(self):
return "no_quarter"
def insert_quarter(self):
# 投幣 並且改變狀態
print("You inserted a quarter")
self.gumball_machine.state = self.gumball_machine.has_quarter_state
def eject_quarter(self):
print("You haven't insert a quarter")
def turn_crank(self):
print("You turned, but there's no quarter")
def dispense(self):
print("You need to pay first")
class HasQuarterState(State):
# 繼承State 類
def __init__(self, gumball_machine):
self.gumball_machine = gumball_machine
def __str__(self):
return "has_quarter"
def insert_quarter(self):
print("You can't insert another quarter")
def eject_quarter(self):
print("Quarter returned")
self.gumball_machine.state = self.gumball_machine.no_quarter_state
def turn_crank(self):
print("You turned...")
self.gumball_machine.state = self.gumball_machine.sold_state
def dispense(self):
print("No gumball dispensed")
class GumballMachine:
def __init__(self, count=0):
self.count = count
# 找出所有狀態,並建立例項變數來持有當前狀態,然後定義狀態的值
self.soldout_state = SoldOutState(self)
self.no_quarter_state = NoQuarterState(self)
self.has_quarter_state = HasQuarterState(self)
self.sold_state = SoldState(self)
if count > 0:
self.state = self.no_quarter_state
else:
self.state = self.soldout_state
def __str__(self):
return ">>> Gumball machine current state: %s" % self.state
def insert_quarter(self):
# 投入25分錢
self.state.insert_quarter()
def eject_quarter(self):
# 退回25分
self.state.eject_quarter()
# print("state", self.state, type(self.state))
def turn_crank(self):
# 轉動曲柄
# print("state", self.state, type(self.state))
self.state.turn_crank()
def release_ball(self):
# 發放糖果
print("A gumball comes rolling out the slot...")
if self.count > 0:
self.count -= 1
if __name__ == "__main__":
# 以下是程式碼測試
gumball_machine = GumballMachine(5) # 裝入5 個糖果
print(gumball_machine)
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
print(gumball_machine)
gumball_machine.insert_quarter() #投入25分錢
gumball_machine.eject_quarter() # 退錢
gumball_machine.turn_crank() # 轉動曲柄
print(gumball_machine)
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
gumball_machine.insert_quarter() # 投入25分錢
gumball_machine.turn_crank() # 轉動曲柄
gumball_machine.eject_quarter() # 退錢
print(gumball_machine)
複製程式碼
重構後的程式碼相對於之前的程式碼做了哪些事情呢?
- 將每個狀態的行為區域性話到自己的類中
- 刪除if 語句
- 將
狀態類
對修改關閉,對糖果季類對擴充套件開放
下圖是剛初始狀態圖示:
上面重構部分程式碼使用的就是狀態模式:
定義
狀態模式
: 狀態模式允許物件在內部狀態改變時改變它的行為,物件看起來好像修改了它的類。
狀態模式的類圖如下:
狀態模式是將多個行為封裝在狀態物件中, context 的行為隨時可委託到其中一個狀態中。當前狀態在不同的狀態物件中改變,以反映出context 內部的狀態,context 的行為也會隨之改變。
擴充套件
如果,現在要在這四個狀態的基礎上再加一個狀態(購買糖果後,有10%的概率再得一個),該如何實現呢?
# 新增WinnerState 類,只有dispense 方法不同,可以從SoldState 類繼承
class WinnerState(SoldState):
def __str__(self):
return "winner"
def dispense(self):
print("You're a WINNER! You get two gumballs for your quarter")
self.gumball_machine.release_ball()
if gumball_machine.count == 0:
self.gumball_machine.state = self.gumball_machine.soldout_state
else:
self.gumball_machine.release_ball()
if gumball_machine.count > 0:
self.gumball_machine.state = self.gumball_machine.no_quarter_state
else:
print("Oops, out of gumballs!")
self.gumball_machine.state = self.gumball_machine.soldout_state
# 修改turn_crank 方法
class HasQuarterState(State):
...
def turn_crank(self):
print("You turned...")
winner = random.randint(0, 9)
if winner == 4 and self.gumball_machine.count > 1: # 如果庫存大於 1 並且隨機數等於4(可以是0到9任意值)
self.gumball_machine.state = self.gumball_machine.winner_state
else:
self.gumball_machine.state = self.gumball_machine.sold_state
# 在 GumballMachine 中初始化
class GumballMachine:
def __init__(self, count=0):
self.count = count
# 找出所有狀態,並建立例項變數來持有當前狀態,然後定義狀態的值
...
self.winner_state = WinnerState(self)
...
複製程式碼
總結
- 狀態模式允許一個物件給予內部狀態而擁有不同的行為
- 狀態模式用類代表狀態
- Context 會將行為委託給當前狀態物件
- 通過將每狀態封裝進一個類,把改變區域性化
- 狀態裝歡可以由State 類或Context 類控制
- 使用狀態模式會增加類的數目
- 狀態類可以被多個Context 例項共享
本文例子來自《Head First 設計模式》。
新年快樂。
最後,感謝女朋友支援和包容,比❤️
也可以在公號輸入以下關鍵字獲取歷史文章:公號&小程式
| 設計模式
| 併發&協程