State Design Pattern is prefered to use in the situation where the functionality of an interface depends upon its current state.
Consider a mobile phone if a mobile phone is connected to the internet it can be used to make video calls, stream online videos or do online web surfing etc., but if a phone is disconnected from the internet it will not be able to do any online stuff.
That is, functionalities offered by the mobile phone depends upon its state — Connected to the internet or not.
State pattern offers to change the behaviour of a class when a class changes its state. State Pattern enables the implementation of somewhat Polymorphic behaviour.
State Pattern helps to avoid the usage of if-else and switch-case conditional logic making our code more robust, loosely coupled and increasing the maintainability.
Components of State Pattern
- Context: Context interface represents entity(i.e class) that can have various states. Context interface have reference to all states possible. And, have a method
request()
to switch between various states possible.
class Context:
_state = None
""" A reference to the current state."""
def __init__(self, state):
self.transition_to(state)
def transition_to(self, state):
"""Method to make transition"""
print(f"Context: Transition to {type(state).__name__}")
self._state = state
self._state.context = self
def request1(self):
self._state.handle1()
def request2(self):
self._state.handle2()
- State interface: An abstract interface which defines a common interface for all concrete states, encapsulating all behaviour associated with a particular state.
class State:
"""An abstract class for Concrete sub-classes"""
@property
def context(self):
return self._context
@context.setter
def context(self, context: Context):
self._context = context
@abstractmethod
def handle1(self):
pass
@abstractmethod
def handle2(self):
pass
- ConcreteState classes: These classes implements their own implementation for the request. When a Context changes state, what really happens is that we have a different ConcreteState associated with it.
class State_A(State):
def handle1(self):
print("State_A handles request1.")
print("State_A wants to change the state of the context.")
self.context.transition_to(State_B())
def handle2(self):
print("State_A handles request2.")
class State_B(State):
def handle1(self):
print("State_B handles request1.")
def handle2(self):
print("State_B handles request2.")
print("State_B wants to change the state of the context.")
self.context.transition_to(State_A())
- The driver code will be like:
if __name__ == "__main__":
# The Driver code.
context = Context(State_A())
context.request1()
context.request2()
Example
Consider a Person. Let a person can have two states Happy and Sad.
- Abstract base class for various states.
class EmotionalState():
@abstractmethod
def say_hello(self):
pass
@abstractmethod
def say_goodbye(self):
pass
- Actual state class
class HappyState(EmotionalState):
def say_goodbye(self):
return "Bye, friend!"
def say_hello(self):
return "Hello, friend!"
class SadState(EmotionalState):
def say_goodbye(self):
return "Bye. Sniff, sniff."
def say_hello(self):
return "Hello. Sniff, sniff."
- Now the Context class Person
class Person(EmotionalState):
def __init__(self, state):
self.state = state
def set_state(self, state):
self.state = state
def say_goodbye(self):
return self.state.say_goodbye()
def say_hello(self):
return self.state.say_hello()
if __name__ == '__main__':
person = Person(HappyState())
print("Hello in happy state: " + person.say_hello())
print("Goodbye in happy state: " + person.say_goodbye())
person.set_state(SadState())
print("Hello in sad state: " + person.say_hello())
print("Goodbye in sad state: " + person.say_goodbye())
Output
Hello in happy state: Hello, friend!
Goodbye in happy state: Bye, friend!
Hello in sad state: Hello. Sniff, sniff.
Goodbye in sad state: Bye. Sniff, sniff.
An object’s behaviour depends on its state, and it must change its behaviour at run-time depending on that state. In such scenarios, the State Design Pattern can be used.