Menu
Coddy logo textTech

ステートパターン

CoddyのPythonジャーニー「Object Oriented Programming」セクションの一部 — レッスン 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

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

challenge icon

チャレンジ

簡単

このチャレンジでは、適切なカプセル化とメソッドを備えた State クラスを state.py に実装します。ファイルには、実装をステップバイステップでガイドするための詳細な TODO コメントが含まれています。

テストスイートは、実装がすべての要件を満たし、例外的な条件を正しく処理することを確認するように設計されています。テスト結果を使用して、期待される動作を理解し、ソリューションを洗練させてください。

チートシート

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 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()}")  # 現在: Red
light.change()  # Red -> Green
print(f"Current: {light.get_color()}")  # 現在: Green

Stateパターンは、状態固有の振る舞いを個別のクラスにカプセル化し、コンテキストオブジェクトが現在の状態に処理を委譲するようにします。これにより、複雑なif/else文が排除され、新しい状態の追加が容易になります。

自分で試してみよう

from state import State

# 包括的なテストケースハンドラー
test_case = input()

if test_case == "basic_test":
    obj = State("Test Name")
    obj.display_info()

elif test_case == "validation_test":
    obj = State("Validation Test")
    print(f"Name: {obj.name}")

elif test_case == "empty_name_test":
    obj = State("")
    obj.display_info()
    print(f"Name property: {obj.name}")

elif test_case == "special_chars_test":
    obj = State("Test@#$%")
    obj.display_info()
    print(f"Name property: {obj.name}")

elif test_case == "long_name_test":
    long_name = "A" * 100
    obj = State(long_name)
    obj.display_info()
    print(f"Name length: {len(obj.name)}")

elif test_case == "multiple_states_test":
    states = [
        State("California"),
        State("Texas"),
        State("New York"),
        State("Florida")
    ]
    
    for state in states:
        state.display_info()
        print(f"Name via property: {state.name}")

elif test_case == "attribute_access_test":
    obj = State("Access Test")
    # プライベート属性への直接アクセス(非推奨)
    print(f"Direct access (not recommended): {obj._name}")
    # プロパティ経由のアクセス(推奨)
    print(f"Property access (recommended): {obj.name}")

elif test_case == "property_behavior_test":
    obj = State("Original Name")
    print(f"Original name: {obj.name}")
    
    # 名前の変更を試みる(セッターがないため失敗する)
    try:
        obj.name = "New Name"
    except AttributeError as e:
        print(f"Error when trying to modify name: {e}")
    
    # 名前が変更されていないことを示す
    print(f"Name after modification attempt: {obj.name}")
quiz icon腕試し

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

Object Oriented Programmingのすべてのレッスン