Introduction
The Servant pattern is a way of organizing code where one special object helps out a bunch of other objects. This helper object provides some functions to these objects, so they don’t have to do the same things over and over again. This pattern makes it easier to add new features and test the code.
Difference between the Servant pattern and Dependency Injection
The Servant pattern helps us keep things separate and make our systems more flexible. It adds extra features to existing objects. On the other hand, Dependency Injection reduces connections between different parts of the code. It gives a class the things it needs to work, usually in a specific form, instead of making the class create them.
Implementation in Python
We’ll make a simple window system using this pattern. There are two types of windows: ones that can rotate and ones that can’t. We create these windows and a helper, the Servant, to make them rotate.
We first define a Rotatable
class which both kinds of windows implement in a different way:
class Rotatable:
def rotate(self, degrees: int) -> bool:
pass
Note that we return a bool, to signal the success of the operation.
The rotable window implementation looks like this:
class RotatableWindow(Rotatable):
_title: str = None
def __init__(self, title: str):
self._title = title
def open(self):
print(f"Opening window: {self._title}")
def close(self):
print(f"Closing window: {self._title}")
def rotate(self, degrees: int) -> bool:
print(f"Rotating window {self._title} by {degrees} degrees")
return True
The open()
and close()
methods speak for themselves, and so does the constructor. The rotate()
prints out a message, and return True
since we can rotate the window.
The non-rotatable looks similar:
class NonRotatableWindow(Rotatable):
_title: str = None
def __init__(self, title: str):
self._title = title
def open(self):
print(f"Opening window: {self._title}")
def close(self):
print(f"Closing window: {self._title}")
def rotate(self, degrees: int) -> bool:
print(f"Cannot rotate window: {self._title}")
return False
The main difference is the message and the return-value of the rotate()
method.
Let’s implement the servant:
class RotationServant:
def perform_rotation(self, window: Rotatable, degrees: int) -> bool:
pass
class WindowRotatorServant(RotationServant):
def perform_rotation(self, window: Rotatable, degrees: int) -> bool:
return window.rotate(degrees)
Some notes:
- The
RotationServant
interface withperform_rotation()
method which has an object that implements theRotatable
interface. - The
perform_rotation()
method returns a bool, to signal the success of the operation.
Testing
Let’s test our setup:
if __name__ == "__main__":
rotator = WindowRotatorServant()
rotatable_window = RotatableWindow("Rotatable window")
rotatable_window.open()
if rotatable_window.rotate(45):
print("Rotation succeeded")
else:
print("Could not rotate window")
rotatable_window.close()
nonrotatable_window = NonRotatableWindow("Non Rotatable window")
nonrotatable_window.open()
if nonrotatable_window.rotate(45):
print("Rotation succeeded")
else:
print("Could not rotate window")
nonrotatable_window.close()
We make a rotatable window and a non-rotatable window, then try to rotate them. The Servant helps the rotatable one rotate, but it can’t help the non-rotatable one.
Conclusion
Even though it was a bit tricky to find a good example, the Servant pattern is a neat way to add features to your classes without changing their code. It’s especially useful when you want to keep different tasks separate, like rotating windows in our example. It makes the code easy to put together and maintain.