Menu
Coddy logo textTech

State Pattern

Part of the Object Oriented Programming section of Coddy's Python journey — lesson 53 of 64.

The State Pattern allows an object to change its behavior when its internal state changes. The object appears to change its class based on its current state.

Here are simple state classes for a traffic light:

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"

Each state defines what happens when transitioning to the next state.

Create a context class that holds the current state:

class TrafficLight:
    def __init__(self):
        self.state = RedState()  # Start with red
    
    def change(self):
        self.state.next_state(self)
    
    def get_color(self):
        return self.state.current_color()

The traffic light delegates behavior to its current state object.

Use the traffic light:

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

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

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

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

Create another example with a simple player:

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()   # Starting music
player.play()   # Already playing
player.stop()   # Stopping music
player.stop()   # Already stopped

Output:

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

Key Point: The State Pattern encapsulates state-specific behavior in separate classes and lets the context object delegate to the current state. When the state changes, the behavior changes automatically. This eliminates complex if/else statements and makes adding new states easier.

challenge icon

Challenge

Easy

In this challenge, you'll implement the State class in state.py with proper encapsulation and methods. The file contains detailed TODO comments to guide your implementation step-by-step.

The test suite is designed to verify your implementation meets all requirements and handles exceptional conditions correctly. Use the test results to understand expected behavior and refine your solution.

Cheat sheet

The State Pattern allows an object to change its behavior when its internal state changes. The object appears to change its class based on its current state.

Create state classes that define behavior for each 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"

Create a context class that holds the current state:

class TrafficLight:
    def __init__(self):
        self.state = RedState()  # Start with red
    
    def change(self):
        self.state.next_state(self)
    
    def get_color(self):
        return self.state.current_color()

Usage example:

light = TrafficLight()
print(f"Current: {light.get_color()}")  # Current: Red
light.change()  # Red -> Green
print(f"Current: {light.get_color()}")  # Current: Green

The State Pattern encapsulates state-specific behavior in separate classes and lets the context object delegate to the current state. This eliminates complex if/else statements and makes adding new states easier.

Try it yourself

from state import State

# Comprehensive test case handler
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")
    # Direct access to private attribute (not recommended)
    print(f"Direct access (not recommended): {obj._name}")
    # Access via property (recommended)
    print(f"Property access (recommended): {obj.name}")

elif test_case == "property_behavior_test":
    obj = State("Original Name")
    print(f"Original name: {obj.name}")
    
    # Try to modify the name (will fail as there's no setter)
    try:
        obj.name = "New Name"
    except AttributeError as e:
        print(f"Error when trying to modify name: {e}")
    
    # Show name is unchanged
    print(f"Name after modification attempt: {obj.name}")
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming