3. 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. 4
Example: Cell cycle
Our system has 5 states:
Start
Interphase
Mitosis
Cytokinesis
End
It has 2 events:
advance
grow
6. 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;
}
}
7. 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. 8
class InterphaseState implements CellState {
public void grow(Cell cell) {
cell.makeProtein();
}
public void advance(Cell cell) {
cell.replicateDNA();
cell.setState(new MitosisState());
}
}
9. 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. 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. 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. 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. 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
19. 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.
21. 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
23. 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. 24
abstract class FrogState {
public FrogState develop() {
throw new IllegalStateException();
}
public FrogState eat() {
throw new IllegalStateException();
}
public FrogState die() {
return new EndState();
}
}
25. 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. 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. 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. 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. 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();