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

Implement the State pattern in state.py for a lamp with one button. Each press moves the lamp through three states in a cycle: off, low, high, and back to off. Instead of an if chain on a string, every state is its own object that knows what happens when the button is pressed.

  • LampState is the base class with press(lamp) and describe().
  • OffState, LowState and HighState implement them: press calls lamp.set_state(...) with the next state (off to low, low to high, high to off) and describe returns Lamp is off, Lamp is on low or Lamp is on high.
  • Lamp is the context: it starts in OffState, set_state swaps the state object, press_button delegates to the current state, and status returns the current description.

driver.py is locked: it presses the button and prints the status. Notice that Lamp never checks which state it is in; the state objects decide.

Try it yourself

from state import Lamp

# Test case handler (do not change)
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 iconTest yourself

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

All lessons in Object Oriented Programming

Practice on your own: Online Python compiler