State Pattern

📢 This article was translated by gemini-3-flash-preview

State Pattern (Object Behavioral Pattern)

Intent

Allows an object to change its behavior when its internal state changes. The object will appear to have modified its class.

Structure

State Pattern

Participants:

  • Context: Defines the interface of interest to clients. It maintains an instance of a ConcreteState subclass that defines the current state.

  • State: Defines an interface for encapsulating the behavior associated with a particular state of the Context.

  • ConcreteState: Each subclass implements a behavior associated with a state of the Context.

Applicability

  • An object’s behavior depends on its state, and it must change its behavior at runtime depending on that state.

  • Operations have large, multipart conditional statements that depend on the object’s state. This state is usually represented by one or more enumerated constants. Often, several operations will contain this same conditional structure. The State pattern puts each branch of the conditional into a separate class. This lets you treat the object’s state as an object in its own right that can vary independently from other objects.

Example 1

A large shopping mall has installed several simple tissue vending machines. They sell one pack of tissues for 2 dollars, dispensing only one pack at a time. The state diagram for the vending machine is shown below:

State Pattern-Example 1-Fig 1

Using the State pattern to implement this vending machine results in the following class diagram. The State class is abstract, defining interfaces for inserting coins, ejecting coins, and dispensing tissues. The classes SoldState, SoldOutState, NoQuarterState, and HasQuarterState correspond to the four states: Sold, Sold Out, No Quarter, and Has Quarter (2 dollars).

State Pattern-Example 1-Fig 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.util.*;

interface State {
    public void insertQuarter();    // Insert coin
    public void ejectQuarter();     // Eject coin
    public void turnCrank();        // Press "Dispense" button
    public void dispense();         // Dispense tissue
}
class TissueMachine {
    State soldOutState, noQuarterState, hasQuarterState, soldState, state;
    state = soldOutState;
    int count = 0; // Number of tissues
    public TissueMachine(int numbers) { /* Implementation omitted */ }
    public State getHasQuarterState() { return hasQuarterState; }
    public State getNoQuarterState() { return noQuarterState; }
    public State getSoldState() { return soldState; }
    public State getSoldOutState() { return soldOutState; }
    public int getCount() { return count; }
// Remaining code omitted
}

class NoQuarterState implements State {
    TissueMachine tissueMachine;
    public void insertQuarter() {
        tissueMachine.setState(tissueMachine.getHasQuarterState());
    }
// Constructor and remaining code omitted
}

class HasQuarterState implements State {
    TissueMachine tissueMachine;
    public void ejectQuarter() {
        tissueMachine.setState(tissueMachine.getNoQuarterState());
    }
// Constructor and remaining code omitted
}

class SoldState implements State {
    TissueMachine tissueMachine;
    public void dispense() {
        if (tissueMachine.getCount() > 0) {
            tissueMachine.setState(tissueMachine.getNoQuarterState());
        } else {
            tissueMachine.setState(tissueMachine.getSoldOutState()); }
    }
}

Example 2

An airline’s frequent flyer system classifies members into three levels: Basic, Silver, and Gold. Non-members can apply to become Basic members. Membership levels are adjusted based on accumulated mileage within a year. The state diagram for membership adjustment is shown below:

State Pattern-Example 2-Fig 1

Implementing this scenario using the State pattern results in the following class diagram:

State Pattern-Example 2-Fig 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.util.*;

abstract class CState {
    public int flyMiles; // Mileage
    public abstract double travel(int miles, CFrequentFlyer context);          // Adjust membership level based on accumulated mileage
}

class CNoCustomer extends CState { // Non-member
    public double travel(int miles, CFrequentFlyer context) {
        System.out.println("Your travel will not account for points");
        return miles; // No mileage accumulation
    }
}

class CBasic extends CState { // Basic member
    public double travel(int miles, CFrequentFlyer context) {
        if (context.flyMiles >= 25000 && context.flyMiles < 50000)
            context.setState(new CSilver());
        if (context.flyMiles >= 50000)
            context.setState(new CGold());
        return miles;
    }
}

class CGold extends CState { // Gold member
    public double travel(int miles, CFrequentFlyer context) {
        if (context.flyMiles >= 25000 && context.flyMiles < 50000)
            context.setState(new CSilver());
        if (context.flyMiles < 25000);
        context.setState(new CBasic());
        return miles + 0.5 * miles; // Accumulated mileage bonus
    }
}

class CSilver extends CState { // Silver member
    public double travel(int miles, CFrequentFlyer context) {
        if (context.flyMiles <= 25000)
            context.setState(new CBasic());
        if (context.flyMiles >= 50000)
            context.setState(new CGold());
        return (miles + 0.25 * miles); // Accumulated mileage bonus
    }
}

class CFrequentFlyer {
    CState state;
    double flyMiles;
    public CFrequentFlyer() {
        state = new CNoCustomer();
        flyMiles = 0;
        setState(state);
    }
    public void setState(CState state) { this.state = state; }
    public void travel(int miles) {
        double bonusMiles = state.travel(miles, this);
        flyMiles = flyMiles + bonusMiles;
    }
}

Example 3

Vending machine: In stock vs. Out of stock.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class StatePattern {
    public static void main(String[] args) {
        Context context = new Context();

        context.Request(); // count = 2
        context.Request(); // count = 1
        context.Request(); // count = 0

        context.Request(); // switch to State A

        context.Request(); // count = 4
    }
}

class Context{ // Vending Machine
    private int count;
    private State state;

    public Context(){
        count = 3;
        state = new StateA();
    }

    public int getCount() {
        return count;
    }

    public State getState() {
        return state;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public void setState(State state) {
        this.state = state;
    }

    public void Request(){ // Purchase drink
        state.Handle(this);
    }
}

interface State{
    public void Handle(Context context);
}

class StateA implements State{ // In stock
    @Override
    public void Handle(Context context){
        int count = context.getCount();

        if(count >= 1){
            context.setCount(count - 1);
            System.out.println("Complete! " + context.getCount() + " left");
            if(context.getCount() == 0){
                context.setState(new StateB());
            }
        }else{
            System.out.println("Refused!");
        }
    }
}

class StateB implements State{ // Out of stock
    @Override
    public void Handle(Context context){
        int count = context.getCount();

        if(count == 0){
            System.out.println("Refused!");
            context.setCount(5);
            System.out.println("Please try again");
            context.setState(new StateA());
        }else {
            context.setState(new StateA());
            context.Request();
        }
    }
}