Menu
Coddy logo textTech

ステートパターン

CoddyのPythonジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 53/64。

State パターンを使用すると、内部状態が変化したときにオブジェクトの振る舞いを変更できます。オブジェクトは、現在の状態に基づいてクラスが変化したように見えます。

信号機のシンプルな状態クラスは次のとおりです:

class RedState:
    def next_state(self, light):
        print("Red -> Green")
        light.state = GreenState()
    
    def current_color(self):
        return "Red"

class GreenState:
    def next_state(self, light):
        print("Green -> Yellow")
        light.state = YellowState()
    
    def current_color(self):
        return "Green"

class YellowState:
    def next_state(self, light):
        print("Yellow -> Red")
        light.state = RedState()
    
    def current_color(self):
        return "Yellow"

各状態は、次の状態へ遷移するときに何が起こるかを定義します。

現在の状態を保持するコンテキストクラスを作成します。

class TrafficLight:
    def __init__(self):
        self.state = RedState()  # 赤から開始
    
    def change(self):
        self.state.next_state(self)
    
    def get_color(self):
        return self.state.current_color()

信号機は、現在の状態オブジェクトに振る舞いを委譲します。

信号機を使います:

light = TrafficLight()
print(f"Current: {light.get_color()}")

light.change()  # 赤 -> 緑
print(f"Current: {light.get_color()}")

light.change()  # 緑 -> 黄
print(f"Current: {light.get_color()}")

light.change()  # 黄 -> 赤
print(f"Current: {light.get_color()}")

シンプルなプレイヤーを使った別の例を作成しましょう:

class PlayingState:
    def play(self, player):
        print("Already playing")
    
    def stop(self, player):
        print("Stopping music")
        player.state = StoppedState()

class StoppedState:
    def play(self, player):
        print("Starting music")
        player.state = PlayingState()
    
    def stop(self, player):
        print("Already stopped")

class MusicPlayer:
    def __init__(self):
        self.state = StoppedState()
    
    def play(self):
        self.state.play(self)
    
    def stop(self):
        self.state.stop(self)

player = MusicPlayer()
player.play()   # 音楽を開始しています
player.play()   # すでに再生中です
player.stop()   # 音楽を停止しています
player.stop()   # すでに停止しています

出力:

Current: Red
Red -> Green
Current: Green
Green -> Yellow
Current: Yellow
Yellow -> Red
Current: Red
Starting music
Already playing
Stopping music
Already stopped

重要なポイント:State パターンは、状態固有の振る舞いを個別のクラスにカプセル化し、コンテキストオブジェクトが現在の状態に処理を委譲できるようにします。状態が変わると、振る舞いも自動的に変化します。これにより、複雑な if/else 文が不要になり、新しい状態を追加しやすくなります。

challenge icon

チャレンジ

簡単

1つのボタンを持つランプのために、state.py で State パターンを実装してください。ボタンを押すたびに、ランプは3つの状態を順番に循環します: 消灯、そして再び消灯に戻ります。文字列に対する if の連鎖の代わりに、それぞれの状態を独自のオブジェクトにし、ボタンが押されたときに何が起きるかをそのオブジェクト自身が把握します。

  • LampState は、press(lamp)describe() を持つ基底クラスです。
  • OffStateLowStateHighState はそれらを実装します。press は次の状態(消灯から弱、弱から強、強から消灯)を指定して lamp.set_state(...) を呼び出し、describeLamp is offLamp is on low、または Lamp is on high を返します。
  • Lamp はコンテキストです。OffState で開始し、set_state は状態オブジェクトを切り替え、press_button は現在の状態に処理を委譲し、status は現在の説明を返します。

driver.py はロックされています。ボタンを押してステータスを出力します。Lamp は自分がどの状態にあるかを決して確認せず、状態オブジェクトが判断することに注目してください。

自分で試してみよう

from state import Lamp

# テストケースハンドラー(変更しないでください)
test_case = input()
lamp = Lamp()

if test_case == "initial_test":
    print(lamp.status())
elif test_case == "one_press_test":
    lamp.press_button()
    print(lamp.status())
elif test_case == "cycle_test":
    for _ in range(4):
        print(lamp.status())
        lamp.press_button()
elif test_case == "many_presses_test":
    for _ in range(7):
        lamp.press_button()
    print(lamp.status())
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Pythonオンラインコンパイラ