Empower Code: Master the Potent Servant Pattern in Python

Photo by Pixabay: https://www.pexels.com/photo/blur-breakfast-chef-cooking-262978/

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:

  1. The RotationServant interface with perform_rotation() method which has an object that implements the Rotatable interface.
  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *