Design Patterns @ Python – State 狀態模式筆記

設計模式–狀態模式(State Pattern)

什麼是 State Pattern?

  • State Pattern 是一種行為型設計模式,它允許物件在不同狀態之間切換,並根據當前狀態執行不同的行為。這種模式有助於將狀態相關的邏輯封裝在不同的狀態類別中,使程式碼更具可讀性和可維護性。
  • 在許多情況下,一個物件的行為取決於一個或者多個動態變化的屬性。例如,一個自動販賣機的行為取決於其目前的狀態,例如空閒、有零錢、售罄、正在售出。
  • State Pattern 可以解決這種問題。它將與狀態相關的行為抽取到獨立的狀態類中,讓原物件將工作委派給這些類的實例,而不是自行進行處理。

State Pattern 重點歸納

  • 狀態模式的核心思想是將物件的狀態抽象成不同的類別,每個類別代表一個狀態。
  • 物件持有一個當前狀態的參考,並根據當前狀態執行相應的操作。
  • 狀態之間的轉換由狀態類別負責,而不是由物件自身處理。

State Pattern Class Diagram

State Pattern Python Example01

  • 這是 Dmitri 老師在狀態模式課程上的第一個範例,老師強調這是一個經典的狀態模式實作的範例,但老師個人並不推薦,因為如果以這樣形式來擴展,雖然看似層次分明,但實則透過這樣方式產生的狀態類別會非常多,會讓程式變得複雜且可能降低效能。不過從學習角度來看,透過這樣經典範例,比較能清楚了解狀態模式的企圖是什麼? 因此我還是把這個範例方上來,以方便解析狀態模式的實作過程。首先,在這個例子中,實現了一個簡單的電源控制開關模擬,它(電源)有兩個狀態 On 和 Off,以及一個 Switch 來作為 Context 上下文來當作客戶端。Context 類封裝了當前狀態(初始化時給了當前的初始(關閉)狀態 OffState),並委派給當前狀態的對象(OnState 和 OffState)來處理請求。狀態對象在處理請求時可以引起上下文的狀態切換,狀態切換會依據狀態對象的切換順序安排,從一個狀態過渡到另一種狀態。
from abc import ABC

class Switch:
    def __init__(self):
        self.state = OffState()

    def on(self):
        self.state.on(self)

    def off(self):
        self.state.off(self)


class State(ABC):
    def on(self, switch):
        print('Light is already on')

    def off(self, switch):
        print('Light is already off')


class OnState(State):
    def __init__(self):
        print('Light turned on')

    def off(self, switch):
        print('Turning light off...')
        switch.state = OffState()


class OffState(State):
    def __init__(self):
        print('Light turned off')

    def on(self, switch):
        print('Turning light on...')
        switch.state = OnState()

if __name__ == '__main__':
    sw = Switch()

    sw.on()  # Turning light on...
             # Light turned on

    sw.off()  # Turning light off...
              # Light turned off

    sw.off()  # Light is already off
# 執行結果:
Light turned off
Turning light on...
Light turned on
Turning light off...
Light turned off
Light is already off

State Pattern Python Example02

  • 第二個範例是來自圖靈星球狀態模式視頻課程的 Python 版本,一般在一個 遊戲 App 的設計中,其實中心角色就會有很多的狀態需要管理,而這樣的狀態管理需求就很適合使用狀態模式來實作,而這個範例就是一個簡單的遊戲狀態管理的示範,和上面第一個範例類似,Character 類一樣是一個Context 對象封装了 State,並在初始化時,設定了初始的狀態(值),而 XXXState 則是一個狀態對象,負責接受委託的狀態请求,並將狀態切換到請求的狀態。 而透過 XXXState 狀態對象的中間層,可以加入規則來實現狀態管理。
class State:
    def walk(self):
        pass

    def run(self):
        pass

    def jump(self):
        pass

    def idle(self):
        pass


class IdleState(State):
    def __init__(self, character):
        self.character = character

    def walk(self):
        self.character.state = self.character.walking_state
        print("The character is now walking.")

    def run(self):
        print("The character cannot run right now.")

    def jump(self):
        print("The character cannot jump right now.")

    def idle(self):
        print("The character is already idle.")


class WalkingState(State):
    def __init__(self, character):
        self.character = character

    def walk(self):
        print("The character is already walking.")

    def run(self):
        self.character.state = self.character.running_state
        print("The character is now running.")

    def jump(self):
        print("The character cannot jump right now.")

    def idle(self):
        self.character.state = self.character.idle_state
        print("The character is now idle.")


class RunningState(State):
    def __init__(self, character):
        self.character = character

    def walk(self):
        print("The character cannot walk right now.")

    def run(self):
        print("The character is already running.")

    def jump(self):
        self.character.state = self.character.jumping_state
        print("The character is now jumping.")

    def idle(self):
        self.character.state = self.character.idle_state
        print("The character is now idle.")


class JumpingState(State):
    def __init__(self, character):
        self.character = character

    def walk(self):
        print("The character cannot walk right now.")

    def run(self):
        print("The character cannot run right now.")

    def jump(self):
        print("The character is already jumping.")

    def idle(self):
        self.character.state = self.character.idle_state
        print("The character is now idle.")


class Character:
    def __init__(self):
        self.idle_state = IdleState(self)
        self.walking_state = WalkingState(self)
        self.running_state = RunningState(self)
        self.jumping_state = JumpingState(self)

        self.state = self.idle_state

    def walk(self):
        self.state.walk()

    def run(self):
        self.state.run()

    def jump(self):
        self.state.jump()

    def idle(self):
        self.state.idle()


if __name__ == "__main__":
    character = Character()

    character.walk()  # The character is now walking.
    character.run()  # The character is now running.
    character.jump()  # The character is now jumping.
    character.idle()  # The character is now idle.

    character.run()  # The character cannot run right now.
    character.jump()  # The character cannot jump right now.
# 執行結果:
The character is now walking.
The character is now running.
The character is now jumping.
The character is now idle.
The character cannot run right now.
The character cannot jump right now.

State Pattern 的優缺點及 Summary

  • 優點:
    • 將狀態特定的行為局部化到各個 State 類中,提高了對代碼的可維護性。
    • 避免 Context 類中過多的條件判斷。
    • 可以在運行時按照狀態改變對象的行為。
    • 開閉原則。新增 State 很方便,不影響已有的代码。
  • 缺點:
    • 會產生大量的 State 子類。
    • 使程序運行變得複雜。
    • 状态转换逻辑复杂时不太容易维护。
  • 總結:
    • State Pattern 將每個狀態的行為抽象成一個類,並將狀態的切換邏輯從 Context 移到各個 State 中, 通過將特定狀態相關的行為局部化到一個類中,避免 Context 中過多的條件判斷,提高了代碼的可維護性。它可以在運行時根據狀態改變對象的行為。但是也會產生大量的 State 子類。需要權衡使用。
1個讚