Skip to content

State Design Pattern

Video Lecture

Section Video Links
State Pattern State State Pattern  
State Use Case State Use Case State Use Case  

Overview

Not to be confused with object state, i.e., one of more attributes that can be copied as a snapshot, the State Pattern is more concerned about changing the handle of an object's method dynamically. This makes an object itself more dynamic and may reduce the need of many conditional statements.

Instead of storing a value in an attribute, and then using conditional statements within an objects' method to produce different output, a subclass is assigned as a handle instead. The object/context doesn't need to know about the inner working of the assigned subclass that the task was delegated to.

In the state pattern, the behavior of an objects state is encapsulated within the subclasses that are dynamically assigned to handle it.

Terminology

  • State Interface: An interface for encapsulating the behavior associated with a particular state of the Context.
  • Concrete Subclasses: Each subclass implements a behavior associated with the particular state.
  • Context: This is the object where the state is defined, but the execution of the state behavior is redirected to the concrete subclass.

State UML Diagram

State UML Diagram

Source Code

In the concept example, there are three possible states. Every time the request() method is called, the concrete state subclass is randomly selected by the context.

./src/state/state-concept.ts

 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
// The State Pattern Concept

class Context {
    // This is the object whose behavior will change
    #stateHandles: IState[]
    #handle: IState | undefined

    constructor() {
        this.#stateHandles = [
            new ConcreteStateA(),
            new ConcreteStateB(),
            new ConcreteStateC(),
        ]
        this.#handle = undefined
    }

    request() {
        // A method of the state that dynamically changes which
        // class it uses depending on the value of this.#handle
        this.#handle = this.#stateHandles[Math.floor(Math.random() * 3)]
        return this.#handle
    }
}

interface IState {
    // A State Interface
    toString(): string
}

class ConcreteStateA implements IState {
    // A ConcreteState Subclass

    toString() {
        return 'I am ConcreteStateA'
    }
}

class ConcreteStateB implements IState {
    // A ConcreteState Subclass

    toString() {
        return 'I am ConcreteStateB'
    }
}

class ConcreteStateC implements IState {
    // A ConcreteState Subclass

    toString() {
        return 'I am ConcreteStateC'
    }
}

// The Client
const CONTEXT = new Context()
console.log(CONTEXT.request())
console.log(CONTEXT.request())
console.log(CONTEXT.request())
console.log(CONTEXT.request())
console.log(CONTEXT.request())

Output

node ./dist/state/state-concept.js
ConcreteStateB {}
ConcreteStateA {}
ConcreteStateC {}
ConcreteStateA {}
ConcreteStateC {}

State Use Case

This example takes the concept example further and instead assigns then next state in sequence rather than choosing the states subclasses randomly.

It also allows to set the state outside the context by using a getter/setter.

The client will set the state, and then run a request, and then change the state again, etc., and depending on the state, the behavior of the method would have changed.

State Example Use Case UML Diagram

State Example Use Case UML Diagram

Source Code

./src/state/client.ts

 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
82
83
// The State Use Case Example

enum ExampleState {
    Initializing = 'Initializing',
    Started = 'Started',
    Running = 'Running',
    Finished = 'Finished',
}

interface IExampleState {
    // A State Interface
    request(): void
}

class StateContext implements IExampleState {
    #state: ExampleState

    constructor() {
        this.#state = ExampleState.Initializing
    }

    public get state() {
        return this.#state
    }

    public set state(value: ExampleState) {
        switch (value) {
            case ExampleState.Started:
                this.request = Started.prototype.request
                break
            case ExampleState.Running:
                this.request = Running.prototype.request
                break
            case ExampleState.Finished:
                this.request = Finished.prototype.request
                break
        }
        this.#state = value
    }

    request() {
        // Does nothing until state changes, when then
        // this method handle is reassigned to a different
        // concrete states request method
    }
}

class Started implements IExampleState {
    // A ConcreteState Subclass
    request() {
        console.log(`I am now Started`)
    }
}

class Running implements IExampleState {
    // A ConcreteState Subclass
    request() {
        console.log(`I am now Running`)
    }
}

class Finished implements IExampleState {
    // A ConcreteState Subclass
    request() {
        console.log(`I am now Finished`)
    }
}

// The Client
const STATE_CONTEXT = new StateContext()
console.log('STATE_CONTEXT = ' + STATE_CONTEXT.state)
STATE_CONTEXT.state = ExampleState.Started
STATE_CONTEXT.request()
STATE_CONTEXT.state = ExampleState.Running
STATE_CONTEXT.request()
STATE_CONTEXT.state = ExampleState.Finished
STATE_CONTEXT.request()
STATE_CONTEXT.state = ExampleState.Started
STATE_CONTEXT.request()
STATE_CONTEXT.state = ExampleState.Running
STATE_CONTEXT.request()
STATE_CONTEXT.state = ExampleState.Finished
STATE_CONTEXT.request()

Output

node ./dist/state/client.js
STATE_CONTEXT = Initializing
I am now Started
I am now Running
I am now Finished
I am now Started
I am now Running
I am now Finished

Summary

  • Makes an object change its behavior when its internal state changes.
  • The client and the context are not concerned about the details of how the state is created/assembled/calculated. The client will call a method of the context, and it will be handled by a subclass.
  • The State pattern appears very similar to the Strategy pattern, except in the State pattern, the object/context has changed to a different state and will run a different subclass depending on that state.