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
EasyLet'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 classTrafficLightStatewith two methods:String getColor()that returns the current light color, andvoid next(TrafficLight light)that transitions the light to its next state. Implement three concrete state classes:RedState- returnsREDas its color and printsStop! Light is red.whennext()is called, then transitions toGreenStateGreenState- returnsGREENas its color and printsGo! Light is green.whennext()is called, then transitions toYellowStateYellowState- returnsYELLOWas its color and printsCaution! Light is yellow.whennext()is called, then transitions toRedState
TrafficLightclass (the context) that starts inRedState. It should have asetState()method to change the current state, agetColor()method that delegates to the current state, and achange()method that callsnext()on the current state.main.dart: Import your traffic light file and demonstrate the State pattern in action. Create aTrafficLightand print its initial color. Then callchange()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.
REDCheat 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
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor12Async OOP
Futures & async/awaitStreams BasicsStream ControllersAsync ConstructorsAsync in Class MethodsRecap - Data Fetcher15Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern