1
State Pattern
Antony Quinn
2
Structure
Intent
Example
UML structure
Benefits and drawbacks
Exercise
3
Intent
Allow an object to alter its behaviour
when its internal state changes. The
object will appear to change its class.
Also known as
Objects for states
4
Example: Cell cycle
Our system has 5 states:
Start
Interphase
Mitosis
Cytokinesis
End
It has 2 events:
advance
grow
5
Sample Code
6
public class Cell {
private CellState state = new StartState();
public void grow() {
state.grow(this);
}
public void advance() {
state.advance(this);
}
// Package-private
void setState(CellState newState) {
if (state != newState) {
System.out.println(toString(state) + " -> " + toString(newState));
state = newState;
}
}
// Plus: makeProtein(), replicateDNA(), divideNucleus(), divideCytoplasm()
7
interface CellState {
/** @throws IllegalStateException*/
public void grow(Cell cell);
/** @throws IllegalStateException*/
public void advance(Cell cell);
}
class StartState implements CellState {
public void grow(Cell cell) {
throw new IllegalStateException();
}
public void advance(Cell cell) {
cell.setState(new InterphaseState());
}
}
8
class InterphaseState implements CellState {
public void grow(Cell cell) {
cell.makeProtein();
}
public void advance(Cell cell) {
cell.replicateDNA();
cell.setState(new MitosisState());
}
}
9
class MitosisState implements CellState {
public void grow(Cell cell) {
throw new IllegalStateException();
}
public void advance(Cell cell) {
cell.divideNucleus();
cell.setState(new CytokinesisState());
}
}
10
class CytokinesisState implements CellState {
public void grow(Cell cell) {
throw new IllegalStateException();
}
public void advance(Cell cell) {
cell.divideCytoplasm();
cell.setState(new EndState());
}
}
11
class EndState implements CellState {
public void grow(Cell cell) {
throw new IllegalStateException("Dead cells can't grow");
}
public void advance(Cell cell) {
throw new IllegalStateException("Dead cells can't advance");
}
}
12
public class TestCell {
public static void main(String[] args) {
Cell cell = new Cell();
cell.advance(); // Interphase
cell.grow();
cell.grow();
cell.advance(); // Mitosis
cell.advance(); // Cytokinesis
cell.advance(); // End
cell.grow(); // error
}
}
13
Applicability Use the State pattern when
An object's behaviour depends on its
state and must change its behaviour at
run-time depending on that state
Operations have large, multipart
conditional statements that depend on
the object's state, typically switch or
if-else-if constructs
14
Structure
15
Consequences Benefits
Localises state-specific behaviour and
partitions behaviour for different states
Makes state transitions explicit
State objects can be shared
Drawbacks
Lots of classes
16
Known Uses Java
JTable selection
Java Media Framework (JMF)
EBI
UniProt automated annotation (Ernst,
Dani and Michael)
17
Question
At what level of complexity would you
refactor code to use the State Pattern?
18
Exercise
Design a Frog class that
contains the state
machine on the left.
19
Solution
Use an abstract class for common
functionality (but in general we “favor
composition over inheritance” - see the
Strategy pattern)
Start state is transitional, so we can skip it.
20
Solution 1
21
Alternative solution
Could instead let the state's methods return the
new state
Advantages:
No dependency between FrogState and Frog
(looser coupling)
setState is private in Frog
22
Solution 2
23
public class Frog {
private FrogState state = new EmbryoState();
public void develop() {
setState(state.develop());
}
public void eat() {
setState(state.eat());
}
public void die() {
setState(state.die());
}
private void setState(FrogState newState) {
if (state != newState) {
System.out.println(state + " -> " + newState);
state = newState;
}
24
abstract class FrogState {
public FrogState develop() {
throw new IllegalStateException();
}
public FrogState eat() {
throw new IllegalStateException();
}
public FrogState die() {
return new EndState();
}
}
25
class EmbryoState extends FrogState {
public FrogState develop() {
return new TadpoleState();
}
}
class TadpoleState extends FrogState {
public FrogState develop() {
return new AdultState();
}
public FrogState eat() {
System.out.println("Eating algae.");
return this;
}
}
26
class AdultState extends FrogState {
public FrogState eat() {
System.out.println("Eating flies.");
return this;
}
}
class EndState extends FrogState {
public FrogState die() {
throw new IllegalStateException("Dead frogs can't die.");
}
}
27
public class TestFrog {
public static void main(String[] args) {
Frog frog = new Frog(); // Embryo
frog.develop(); // Tadpole
frog.eat();
frog.develop(); // Adult
frog.eat();
frog.eat();
frog.die(); // Dead frog
frog.die(); // Error
}
}
28
import junit.framework.TestCase;
public class EmbryoStateTest extends TestCase {
public void testDevelop() {
FrogState state = new EmbryoState();
FrogState nextState = state.develop();
assertEquals(TadpoleState.class, nextState.getClass());
}
public void testEat() {
FrogState state = new EmbryoState();
try {
FrogState nextState = state.eat();
fail("Embryos can't eat.");
}
catch (Exception e) {
assertEquals(IllegalStateException.class, e.getClass());
}
29
import junit.framework.TestCase;
public class TadpoleStateTest extends TestCase {
public void testEat() {
FrogState state = new TadpoleState();
FrogState nextState = state.eat();
assertEquals(TadpoleState.class, nextState.getClass());
}
public void testDevelop() {
FrogState state = new TadpoleState();
FrogState nextState = state.develop();
assertEquals(AdultState.class, nextState.getClass());
}
public void testDie() {
FrogState state = new TadpoleState();
FrogState nextState = state.die();
assertEquals(EndState.class, nextState.getClass());
30
Any questions?
Top Related