大家好,今天給大家介紹一個新的設計模式,這個設計模式非常重要,在我們日常的開發工作當中經常使用。它就是大名鼎鼎的狀態機模式。
狀態機模式非常適合用在複雜的流程或者是系統當中,可以方便我們對系統的某一個狀態進行抽象,這會讓我們編碼具有更強的可讀性以及延展性。
有向圖與DAG
首先和大家解釋一下狀態機當中這個狀態的概念,這裡的狀態指的是我們系統或者是流程當中的某一個狀態。我用我之前做過的一個活動系統來給大家舉一個例子。
比如我們現在要在網上舉辦一些活動,然後吸引使用者來參與。但是在使用者來參與活動的過程當中其實有很多的狀態需要判斷,比如說我們首先要判斷使用者是否已經登入了。如果登入了,還需要判斷使用者之前是否報名過,如果已經報名了,還需要判斷活動是否開始了等等。
那麼,我們就可以抽象出很多的狀態。比如是否登入、是否報名、未登入等等這些都是狀態。這些狀態之間可以通過一些條件進行轉移,比如在初始狀態當中,通過判斷使用者是否登入選擇轉移到未登入狀態或者是報名判斷的狀態上。我們把這其中的邏輯抽象出來,可以得到這麼一張有向無環圖。
幾乎所有的固定流程都可以抽象出這麼一張圖來,這種圖一般被縮寫成DAG(Directed Acyclic Graph)。如果不用狀態機的話,那麼我們需要編寫大量的程式碼來進行判斷。就拿上述的這個邏輯舉例,我們需要至少4層if巢狀的邏輯判斷來實現這麼一個流程。
如果通過if判斷來實現的話,那麼面臨的一個問題就是這個流程是固定的。如果臨時需要改動,那麼必須要修改程式碼,而我們知道不管大小公司,釋出程式碼都是有嚴格的規範的,是不能隨意釋出的。而使用狀態機主要解決的就是這個問題,可以把流程做成可配置的,如果需要臨時修改,只需要修改狀態機的對應配置即可,可以規避掉程式碼層面的修改。
狀態與狀態機
理解了DAG之後,我們再來看看狀態機的定義和解釋。
狀態機的官方定義是:
The intent of the STATE pattern is to distribute state-specific logic across classes that represent an object’s state. 狀態模式會將與狀態有關的邏輯分佈寫在代表物件狀態的類中
這句話英文讀起來還是挺好理解的,中文相對更繞一些。簡而言之,machine是一種抽象的概念,代表某一個流程或者是原理,並不是我們理解的機器。所以狀態機也不是一個機器,它是由多個代表狀態的類組合而成的流程或者說模式。也就是說我們會把DAG當中的每一個節點(狀態)單獨實現成一個類,那麼整個DAG就是一系列狀態類構成的圖。
對於每個狀態類而言,它們的操作應該都是類似的,就是初始化、執行以及轉移。在一些系統當中,甚至可以沒有執行只有轉移。既然所有狀態的操作都是類似的,那麼我們可以對所有的狀態抽象出統一的介面。這裡我們多了一個is_end方法,代表某一個狀態是否是整個流程的結束,如果是的話,我們就不需要繼續轉移了,直接退出即可。
class State:
def __init__(states):
pass
def determine(param):
pass
def operate():
pass
def is_end():
pass
同樣,我們可以實現狀態機的類。
class StateMachine:
def __init__():
self.node = StartState()
def init():
self.node = StartState()
def run(param):
while not self.node.is_end():
self.node = self.node.determine(param)
self.node.operate()
由於狀態之間轉移以及執行的邏輯都被封裝在不同的類當中了,所以對於狀態機而言,裡面的邏輯非常簡單,一般也不需要太大的修改。即使整個流程或者是某一個狀態的條件發生變動, 我們也只需要修改對應節點的程式碼即可,並不會影響整體,非常適合用在那些流程經常發生變動的場景。
最後,我們來看一個狀態機的使用案例。這個案例源於github,是一個將狀態機應用在收音機上的case,具體的細節檢視程式碼即可。
class State:
def scan(self):
# 模擬收音機的儀表盤,只能一個方向轉動
self.pos += 1
if self.pos == len(self.stations):
self.pos = 0
print('Scanning... Station is {} {}'.format(self.stations[self.pos], self.name))
class AmState(State):
# Am 音訊的類
def __init__(self, radio):
self.radio = radio
self.stations = ['1250', '1380', '1510']
self.pos = 0
self.name = 'AM'
def toggle_amfm(self):
# 轉移到Fm
print('Switching to FM')
self.radio.state = self.radio.fmstate
class FmState(State):
# Fm 音訊類
def __init__(self, radio):
self.radio = radio
self.stations = ['81.3', '89.1', '103.9']
self.pos = 0
self.name = 'FM'
def toggle_amfm(self):
# 轉移到Am
print('Switching to AM')
self.radio.state = self.radio.amstate
class Radio:
# 收音機的整體類,也就是狀態機類
def __init__(self):
self.amstate = AmState(self)
self.fmstate = FmState(self)
self.state = self.amstate
def toggle_amfm(self):
self.state.toggle_amfm()
def scan(self):
self.state.scan()
if __name__ == '__main__':
radio = Radio()
actions = [radio.scan] * 2 + [radio.toggle_amfm] + [radio.scan] * 2
actions *= 2
for action in actions:
action()
整個狀態機的設計模式本身並不複雜,更多的是對這個設計理念和思想的理解,程式碼和形式都是表象。
好了,今天的文章就到這裡,衷心祝願大家每天都有所收穫。如果還喜歡今天的內容的話,請來一個三連支援吧~(點贊、關注、轉發)