State is a behavioral design pattern:
š¤š¤š¤š¤š¤
What is State?
State (Pipeline) is a technique that separate use cases (insert_card
, eject_card
, enter_pin
, request_card
) into small stages(no_card_state
, has_card_state
, has_correct_pin_state
)
insert_card
when no_card_state
eject_card
when no_card_state
enter_pin
when no_card_state
request_card
when no_card_state
insert_card
when has_card_state
eject_card
when has_card_state
enter_pin
when has_card_state
request_card
when has_card_state
insert_card
when has_correct_pin_state
eject_card
when has_correct_pin_state
enter_pin
when has_correct_pin_state
request_card
when has_correct_pin_state
It breaks down a complex process into a series of smaller, more manageable stages.
Each stage is responsible for a specific task and passes its output to the next stage.
Why use State?
This pattern is useful when you have a large, complex process that needs to be broken down into smaller, more manageable stages.
- It allows an object to change its behavior based on its internal state.
- It helps to avoid many if/else statements in the code.
- It allows the object to change its behavior without changing its class.
- It is useful when state varies the behavior of some object whose composition includes that state.
Question: I hate the State, what are the alternatives?
Answer: You can just code as normal without thinking about any pattern
- One alternative is the Strategy design pattern, which is used when we want to change the behavior of an object based on the context in which it is used.
- Another alternative is the Command design pattern, which is used when we want to encapsulate a request as an object, thereby letting us parameterize clients with different requests, queue or log requests, and support undoable operations.
- Yet another alternative is the Template Method design pattern, which is used when we want to define the skeleton of an algorithm in a base class, but let subclasses override specific steps of the algorithm without changing its structure.
When to use State?
Question: When should I use State?
Answer: When you have situations like these:
- You have an object that behaves differently depending on its current state.
- The number of states is large and the state-specific code changes frequently.
- You want to avoid complex conditional logic and improve code readability and maintainability.
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
|
class NoCardState(ATMState):
def insert_card(self):
print("š§ Card inserted")
self.atm_machine.state = self.atm_machine.has_card_state
def eject_card(self):
print("š§ No card to eject")
def enter_pin(self, pin):
print("š§ Insert a card first")
def request_cash(self):
print("š§ Insert a card first")
class HasCardState(ATMState):
def insert_card(self):
print("š§ You cannot insert more than one card")
def eject_card(self):
print("š§ Card ejected")
self.atm_machine.state = self.atm_machine.no_card_state
def enter_pin(self, pin):
if pin:
print(f"š§ Pin entered correctly: {pin}")
self.atm_machine.state = self.atm_machine.has_correct_pin_state
else:
print("š§ Wrong pin")
self.atm_machine.wrong_pin_count += 1
if self.atm_machine.wrong_pin_count >= 2:
print("š§ Too many wrong attempts")
self.eject_card()
def request_cash(self):
print("š§ Please enter your pin first")
class HasCorrectPinState(ATMState):
def insert_card(self):
print("š§ You cannot insert more than one card")
def eject_card(self):
print("š§ Card ejected\n")
self.atm_machine.state = self.atm_machine.no_card_state
def enter_pin(self, pin):
print("š§ You already entered your pin")
def request_cash(self):
print("š§ Cash dispensed šµ")
|
Expected Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
š¤ Request cash
š§ Insert a card first
š¤ Insert card
š§ Card inserted
š¤ Enter pin
š§ Pin entered correctly: 123456
š¤ Request cash
š§ Cash dispensed šµ
š¤ Eject card
š§ Card ejected
š¤ Insert card
š§ Card inserted
š¤ Enter pin
š§ Wrong pin
š¤ Enter pin
š§ Wrong pin
š§ Too many wrong attempts
š§ Card ejected
|
How to implement State?
Non-State implementation:
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
|
class ATMMachine:
def __init__(self):
self.card_inserted = False
self.pin_entered = False
self.wrong_pin_count = 0
def insert_card(self):
print("š¤ Insert card")
if self.card_inserted:
print("š§ You cannot insert more than one card")
else:
print("š§ Card inserted")
self.card_inserted = True
def eject_card(self):
print("š¤ Eject card")
if not self.card_inserted:
print("š§ No card to eject")
else:
print("š§ Card ejected\n")
self.card_inserted = False
self.pin_entered = False
self.wrong_pin_count = 0
def enter_pin(self, pin):
print("š¤ Enter pin")
if not self.card_inserted:
print("š§ Insert a card first")
else:
if self.pin_entered:
print("š§ You already entered your pin")
elif pin:
print(f"š§ Pin entered correctly: {pin}")
self.pin_entered = True
else:
print("š§ Wrong pin")
self.wrong_pin_count += 1
if self.wrong_pin_count >= 2:
print("š§ Too many wrong attempts")
self.eject_card()
def request_cash(self):
print("š¤ Request cash")
if not self.card_inserted:
print("š§ Insert a card first")
elif not self.pin_entered:
print("š§ Please enter your pin first")
else:
print("š§ Cash dispensed šµ")
if __name__ == "__main__":
atm = ATMMachine()
atm.request_cash()
atm.insert_card()
atm.enter_pin(123456)
atm.request_cash()
atm.eject_card()
atm.insert_card()
atm.enter_pin(None)
atm.enter_pin(None)
|
State Implementation:
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
class ATMState:
def __init__(self, atm_machine):
self.atm_machine = atm_machine
def insert_card(self):
pass
def eject_card(self):
pass
def enter_pin(self, pin):
pass
def request_cash(self):
pass
class NoCardState(ATMState):
def insert_card(self):
print("š§ Card inserted")
self.atm_machine.state = self.atm_machine.has_card_state
def eject_card(self):
print("š§ No card to eject")
def enter_pin(self, pin):
print("š§ Insert a card first")
def request_cash(self):
print("š§ Insert a card first")
class HasCardState(ATMState):
def insert_card(self):
print("š§ You cannot insert more than one card")
def eject_card(self):
print("š§ Card ejected")
self.atm_machine.state = self.atm_machine.no_card_state
def enter_pin(self, pin):
if pin:
print(f"š§ Pin entered correctly: {pin}")
self.atm_machine.state = self.atm_machine.has_correct_pin_state
else:
print("š§ Wrong pin")
self.atm_machine.wrong_pin_count += 1
if self.atm_machine.wrong_pin_count >= 2:
print("š§ Too many wrong attempts")
self.eject_card()
def request_cash(self):
print("š§ Please enter your pin first")
class HasCorrectPinState(ATMState):
def insert_card(self):
print("š§ You cannot insert more than one card")
def eject_card(self):
print("š§ Card ejected\n")
self.atm_machine.state = self.atm_machine.no_card_state
def enter_pin(self, pin):
print("š§ You already entered your pin")
def request_cash(self):
print("š§ Cash dispensed šµ")
class ATMMachine:
def __init__(self):
self.no_card_state = NoCardState(self)
self.has_card_state = HasCardState(self)
self.has_correct_pin_state = HasCorrectPinState(self)
self.state = self.no_card_state
self.wrong_pin_count = 0
def insert_card(self):
print("š¤ Insert card")
self.state.insert_card()
def eject_card(self):
print("š¤ Eject card")
self.state.eject_card()
def enter_pin(self, pin):
print("š¤ Enter pin")
self.state.enter_pin(pin)
def request_cash(self):
print("š¤ Request cash")
self.state.request_cash()
if __name__ == "__main__":
atm = ATMMachine()
atm.request_cash()
atm.insert_card()
atm.enter_pin(123456)
atm.request_cash()
atm.eject_card()
atm.insert_card()
atm.enter_pin(None)
atm.enter_pin(None)
|
By using the State Design Pattern, we’ve separated the behavior of the ATM into discrete states, which makes it easier to modify the behavior of the ATM without affecting other parts of the code. We’ve also avoided using conditional statements to determine the behavior of the ATM based on the current state. Instead, each state encapsulates its behavior, and the ATM simply delegates its behavior to the current state.
This approach is more flexible and easier to extend. For example, if we wanted to add a new state, such as “out of service,” we could simply create a new class for that state and modify the ATM to transition to that state when needed. Similarly, if we wanted to modify the behavior of an existing state, we could simply modify the relevant class without affecting other parts of the code.
Overall, the State Design Pattern is a powerful tool for managing complex behavior in software systems. It allows us to encapsulate behavior in discrete states, making the system easier to maintain and extend over time.
Source Code