Menu
Coddy logo textTech

State Pattern

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 98 of 110.

The State pattern allows an object to change its behavior when its internal state changes, making it appear as if the object changed its class. Instead of using complex conditionals to handle different states, you encapsulate each state in its own class and delegate behavior to the current state object.

The pattern consists of a Context that maintains a reference to the current state, and State classes that define behavior for each possible state. When the context's state changes, its behavior changes automatically:

abstract class DocumentState {
  void publish(Document doc);
  String getStatus();
}

class DraftState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Moving to review...');
    doc.setState(ReviewState());
  }

  @override
  String getStatus() => 'Draft';
}

class ReviewState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Document published!');
    doc.setState(PublishedState());
  }

  @override
  String getStatus() => 'Under Review';
}

class PublishedState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Already published.');
  }

  @override
  String getStatus() => 'Published';
}

class Document {
  DocumentState _state = DraftState();

  void setState(DocumentState state) => _state = state;
  void publish() => _state.publish(this);
  String getStatus() => _state.getStatus();
}

void main() {
  var doc = Document();
  print(doc.getStatus());  // Draft
  doc.publish();           // Moving to review...
  print(doc.getStatus());  // Under Review
  doc.publish();           // Document published!
  print(doc.getStatus());  // Published
}

Each state class handles the publish() action differently and can transition the document to the next state. The Document class doesn't need conditionals to check its current state - it simply delegates to whatever state object is currently active. This pattern is ideal for objects with distinct behavioral modes, like order processing, media players, or workflow systems.

challenge icon

Challenge

Easy

Let's build a traffic light system using the State pattern! You'll create a traffic light that cycles through different states, where each state determines the light's color and how it transitions to the next state.

You'll organize your code into two files:

  • traffic_light.dart: This file contains your state classes and the traffic light context. Create an abstract class TrafficLightState with two methods: String getColor() that returns the current light color, and void next(TrafficLight light) that transitions the light to its next state. Implement three concrete state classes:
    • RedState - returns RED as its color and prints Stop! Light is red. when next() is called, then transitions to GreenState
    • GreenState - returns GREEN as its color and prints Go! Light is green. when next() is called, then transitions to YellowState
    • YellowState - returns YELLOW as its color and prints Caution! Light is yellow. when next() is called, then transitions to RedState
    Then create a TrafficLight class (the context) that starts in RedState. It should have a setState() method to change the current state, a getColor() method that delegates to the current state, and a change() method that calls next() on the current state.
  • main.dart: Import your traffic light file and demonstrate the State pattern in action. Create a TrafficLight and print its initial color. Then call change() three times, printing the color after each change. This will show the light cycling through all states and back to the beginning.

Notice how the TrafficLight class doesn't need any conditionals to determine behavior - each state knows its own color and which state comes next. The traffic light simply delegates to whatever state is currently active!

Expected output:

RED
Stop! Light is red.
GREEN
Go! Light is green.
YELLOW
Caution! Light is yellow.
RED

Cheat sheet

The State pattern allows an object to change its behavior when its internal state changes. Instead of using complex conditionals, each state is encapsulated in its own class.

The pattern consists of:

  • Context: Maintains a reference to the current state
  • State classes: Define behavior for each possible state

Basic structure:

abstract class DocumentState {
  void publish(Document doc);
  String getStatus();
}

class DraftState implements DocumentState {
  @override
  void publish(Document doc) {
    print('Moving to review...');
    doc.setState(ReviewState());
  }

  @override
  String getStatus() => 'Draft';
}

class Document {
  DocumentState _state = DraftState();

  void setState(DocumentState state) => _state = state;
  void publish() => _state.publish(this);
  String getStatus() => _state.getStatus();
}

The context delegates behavior to the current state object. Each state class handles actions differently and can transition to the next state. This eliminates the need for conditionals to check the current state.

Ideal for objects with distinct behavioral modes like order processing, media players, or workflow systems.

Try it yourself

import 'traffic_light.dart';

void main() {
  // TODO: Create a TrafficLight instance
  
  // TODO: Print the initial color
  
  // TODO: Call change() and print color (3 times to cycle through all states)
  // First change: RED -> GREEN
  
  // Second change: GREEN -> YELLOW
  
  // Third change: YELLOW -> RED
}
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