Introduction
As many of you may know: Python is not a strongly typed language, unlike C# for example. This is a good thing in many ways, and it can work against you in other ways. Coming from a background in strongly typed languages, I decided to give Python types a try.
In this article I will implement the State patttern. The state pattern is a behavourial state pattern, which allows an object to change its behaviour when its internal state changes. Since this sounds quite cryptic, let’s have a look at the diagram:
A short breakdown:
- Context has two component in this simplified version: a field called state, which is an interface type, and an operation() method.
- When the operation() method is called, the Context object calls the operation() on its state field. Since the state field can hold any implementation of the State interface, the behaviour of Context can change.
As you can see this is not the most complex of Design patterns. That is why I chose it to be the subject of this experiment.
Implementation in Python
We will start by defining the base class for our state pattern:
class State:
def handle(self) -> str:
pass
All this does is define a handle() method which returns a string, denoted by the ‘str’ type.
Next we define the first subclass of this base-class:
class StateA(State):
def handle(self) -> str:
return "StateA"
The handle() method in this example just returns a fixed string.
The seconde State class has a similar implementation:
class StateB(State):
def handle(self) -> str:
return "StateB"
Next we need a Context class which holds this state. The behaviour of the class will change depending on which state is being held:
class Context:
_state: State = None
def __init__(self, initial_state: State = None):
self._state = state
def setstate(self, new_state: State):
self._state = state
def handle(self) -> str:
return self._state.handle()
Line by line:
- We define a private _state variable of type State, which means that it can be assigned objects of type State or subclasses of State. The initial value of this variable is None
- In the constructor, we pass an optional state so that can be set
- The setstate() method is self-explanatory
- In the handle() method we call the handle() method of the _state field.
Because everything is typed now, the code is much clearer. Many IDEs like Jetbrains’ PyCharm have tools to enforce these types and warn against violations. The python interpreter seems to be much more relaxed at this point an only reports errors when running the code: pass a string to the setstate() method and it will report that ‘ ‘str’ object has no attribute ‘handle’
Testing time!
Now we can test our new State pattern:
if __name__ == "__main__":
state = StateA()
context = Context()
context.setstate(state)
print(context.handle())
state = StateB()
context.setstate(state)
print(context.handle())
A short summary
- We construct a state variable and we initialize with a type of StateA
- Next we construct our context. Note that due to the fact we have a default value in our constructor, we do not have to pass a state object.
- We then set a state, and handle it.
- Repeat the same process with a different state
Conclusion
As you can see the implementation of the State pattern is very straightforward. It is also a good example of the Inversion of Control Pattern, since the Context struct only talks to interfaces, never to concrete classes.
It also shows how type annotation can help making your code easier to understand and maintain.
The power of thinking like this and using patterns like this, must never be underestimated. Using a pattern like this makes it possible to build both extendable and maintainable systems.