Card 03/ 08
State Template in Python, Java and TypeScript
Two states that hand control to each other, and a context that never checks which it holds.
The State template is the pattern in its pure form, with the participant names from the class diagram. A context starts in state A and receives three requests; each state handles a request and then replaces itself with the other. The steps build the Python version, and the tabs give the complete program in three languages, each with its output.
Building the template one part at a time
State
One method per request, which receives the context.
@abstractmethod
def handle(self, context: "Context") -> None: ...Concrete state
Does its work, then sets the next state.
def handle(self, context: "Context") -> None:
print("A handles the request, then hands over to B")
context.state = ConcreteStateB()Context
Passes the request on, and never asks which state it holds.
def request(self) -> None:
self.state.handle(self)The complete template
from abc import ABC, abstractmethod
class State(ABC):
@abstractmethod
def handle(self, context: "Context") -> None: ...
class ConcreteStateA(State):
def handle(self, context: "Context") -> None:
print("A handles the request, then hands over to B")
context.state = ConcreteStateB()
class ConcreteStateB(State):
def handle(self, context: "Context") -> None:
print("B handles the request, then hands over to A")
context.state = ConcreteStateA()
class Context:
def __init__(self, state: State) -> None:
self.state = state
def request(self) -> None:
self.state.handle(self)
context = Context(ConcreteStateA())
for _ in range(3):
context.request()
print("now in:", type(context.state).__name__)A handles the request, then hands over to B
B handles the request, then hands over to A
A handles the request, then hands over to B
now in: ConcreteStateBpublic class SaTemplate {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
for (int i = 0; i < 3; i++) {
context.request();
}
System.out.println("now in: " + context.getState().getClass().getSimpleName());
}
}
interface State {
void handle(Context context);
}
class ConcreteStateA implements State {
public void handle(Context context) {
System.out.println("A handles the request, then hands over to B");
context.setState(new ConcreteStateB());
}
}
class ConcreteStateB implements State {
public void handle(Context context) {
System.out.println("B handles the request, then hands over to A");
context.setState(new ConcreteStateA());
}
}
class Context {
private State state;
Context(State state) {
this.state = state;
}
void setState(State state) {
this.state = state;
}
State getState() {
return state;
}
void request() {
state.handle(this);
}
}A handles the request, then hands over to B
B handles the request, then hands over to A
A handles the request, then hands over to B
now in: ConcreteStateBinterface State {
handle(context: Context): void;
}
class ConcreteStateA implements State {
handle(context: Context): void {
console.log("A handles the request, then hands over to B");
context.state = new ConcreteStateB();
}
}
class ConcreteStateB implements State {
handle(context: Context): void {
console.log("B handles the request, then hands over to A");
context.state = new ConcreteStateA();
}
}
class Context {
constructor(public state: State) {}
request(): void {
this.state.handle(this);
}
}
const context = new Context(new ConcreteStateA());
for (let i = 0; i < 3; i++) {
context.request();
}
console.log("now in:", context.state.constructor.name);A handles the request, then hands over to B
B handles the request, then hands over to A
A handles the request, then hands over to B
now in: ConcreteStateBThe three requests alternate between A and B, and the context ends in B. Context.request contains no conditional: the behaviour changed each time because the object behind self.state changed.
Each state receives the context as an argument, which is how it can replace the context's state. The cost is a two-way dependency: the context knows the state interface, and each state knows the context. Java keeps the field private and exposes setState; Python and TypeScript assign the field directly. Both are common, and the choice is how much of the context the states may touch.
← → move through sections and cards