The strategy pattern is all about being able to swap complex functionality in and out, without needing to change large amounts of code. This post summarises the strategy pattern in python. Formal, traditional design patterns are not found in python as often as in other languages such as C# or Java. It can still help to know the general principles and when to look out for opportunities to use them.
Another common design pattern is the command pattern – read about the command pattern in python.
Python Strategy Pattern
The flexibility of python as a language means that you can approach the strategy pattern in different ways. In this post I’ve got an example of a simpler version that uses functions for the strategies, and a less simple version which uses classes for the strategies.
This simple example has two possible strategy functions and a separate function for implementing the strategies.
print("Using first-class function objects") def string_printer(strategy): return strategy def print_lower(input_string): print(input_string.lower()) def print_upper(input_string): print(input_string.upper())
We can pass the strategy function is as an object into the ‘string_printer’ function. This function immediately returns the strategy object, which is then called with the “Hello, World!” argument.
string_printer(print_lower)("Hello, World!") >>> hello, world! string_printer(print_upper)("Hello, World!") >>> HELLO, WORLD!
This might seem like an odd thing to do, and introducing unnecessary complexity. The benefit comes as it means that the ‘string_printer’ implementer can remain the same, while the strategies can change independently or new strategies can be developed.
Less Simple Implementation
By using classes rather than functions we extend the options for encapsulating data and methods in a class, which may be beneficial in some cases.
print("Using classic classes") class LowerCase: def execute(self, input_string): print(input_string.lower()) class UpperCase: def execute(self, input_string): print(input_string.upper()) class FancyCase: def execute(self, input_string): for i, letter in enumerate(input_string): if i % 2 == 0: print(letter.upper(), end="") else: print(letter, end="") print("") class StringPrinter: def __init__(self, strategy): self._strategy = strategy def execute(self, input_string): self._strategy.execute(input_string) def change_strategy(self, new_strategy): self._strategy = new_strategy
Now we can use the specific strategies as plugins for the main operation. We can extend functionality without needing to change the StringPrinter code.
printer = StringPrinter(UpperCase()) printer.execute('Hello, World!') >>> HELLO, WORLD! printer.change_strategy(LowerCase()) printer.execute('Hello, World!') >>> hello, world! printer.change_strategy(FancyCase()) printer.execute('Hello, World!') >>> HeLlO, WOrLd!