Skip to content

State Design Pattern

Video Lecture

Section Video Links
State Overview State Overview State Overview State Overview 
State Use Case State Use Case State Use Case State Use Case 
__call__ Attribute Dunder __call__ Attribute Dunder __call__ Attribute Dunder __call__ Attribute 

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 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

...Refer to Book or Videos for extra content.

./state/state_concept.py

 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
# pylint: disable=too-few-public-methods
"The State Pattern Concept"
from abc import ABCMeta, abstractmethod
import random

class Context():
    "This is the object whose behavior will change"

    def __init__(self):
        self.state_handles = [ConcreteStateA(),
                              ConcreteStateB(),
                              ConcreteStateC()]
        self.handle = None

    def request(self):
        """A method of the state that dynamically changes which
        class it uses depending on the value of self.handle"""
        self.handle = self.state_handles[random.randint(0, 2)]
        return self.handle

class IState(metaclass=ABCMeta):
    "A State Interface"

    @staticmethod
    @abstractmethod
    def __str__():
        "Set the default method"

class ConcreteStateA(IState):
    "A ConcreteState Subclass"

    def __str__(self):
        return "I am ConcreteStateA"

class ConcreteStateB(IState):
    "A ConcreteState Subclass"

    def __str__(self):
        return "I am ConcreteStateB"

class ConcreteStateC(IState):
    "A ConcreteState Subclass"

    def __str__(self):
        return "I am ConcreteStateC"

# The Client
CONTEXT = Context()
print(CONTEXT.request())
print(CONTEXT.request())
print(CONTEXT.request())
print(CONTEXT.request())
print(CONTEXT.request())

Output

1
2
3
4
5
6
python.exe ./state/state_concept.py
I am ConcreteStateB
I am ConcreteStateA
I am ConcreteStateB
I am ConcreteStateA
I am ConcreteStateC

State Example Use Case

...Refer to Book or Videos for extra content.

State Example Use Case UML Diagram

State Example Use Case UML Diagram

Source Code

./state/client.py

 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
# pylint: disable=too-few-public-methods
"The State Use Case Example"
from abc import ABCMeta, abstractmethod


class Context():
    "This is the object whose behavior will change"

    def __init__(self):

        self.state_handles = [
            Started(),
            Running(),
            Finished()
        ]
        self._handle = iter(self.state_handles)

    def request(self):
        "Each time the request is called, a new class will handle it"
        try:
            self._handle.__next__()()
        except StopIteration:
            # resetting so it loops
            self._handle = iter(self.state_handles)


class IState(metaclass=ABCMeta):
    "A State Interface"

    @staticmethod
    @abstractmethod
    def __call__():
        "Set the default method"


class Started(IState):
    "A ConcreteState Subclass"

    @staticmethod
    def method():
        "A task of this class"
        print("Task Started")

    __call__ = method


class Running(IState):
    "A ConcreteState Subclass"

    @staticmethod
    def method():
        "A task of this class"
        print("Task Running")

    __call__ = method


class Finished(IState):
    "A ConcreteState Subclass"

    @staticmethod
    def method():
        "A task of this class"
        print("Task Finished")

    __call__ = method


# The Client
CONTEXT = Context()
CONTEXT.request()
CONTEXT.request()
CONTEXT.request()
CONTEXT.request()
CONTEXT.request()
CONTEXT.request()

Output

1
2
3
4
5
6
python.exe ./state/client.py
Task Started
Task Running
Task Finished
Task Started
Task Running

New Coding Concepts

__call__ Dunder Attribute

Overloading the __call__ attribute makes an instance of a class callable like a function when by default it isn't. You need to call a method within the class directly.

1
2
3
4
5
6
7
class ExampleClass:
    @staticmethod
    def do_this_by_default():
        print("doing this")

EXAMPLE = ExampleClass()
EXAMPLE.do_this_by_default() # needs to be explicitly called to execute

If you want a default method in your class, you can point to it by using by the __call__ attribute.

1
2
3
4
5
6
7
8
9
class ExampleClass:
    @staticmethod
    def do_this_by_default():
        print("doing this")

    __call__ = do_this_by_default

EXAMPLE = ExampleClass()
EXAMPLE() # function now gets called by default

Summary

...Refer to Book or Videos for extra content.