Strategy Pattern Sans Objects and Functions
As many of my readers will likely know, my favorite design pattern is the Decorator Pattern, but I don’t think I’ve mentioned what my second favorite pattern is. This is understandable, as I have a difficult enough time picking favorites usually, let alone second favorites.
Well, my second favorite is sort of a toss up between the Factory Pattern and the Strategy Pattern. I almost HAVE to choose the Strategy Pattern though, since the Factory Pattern is essentially a specialization of the Strategy Pattern.
Today, I am going to present an interesting idea about implementing the Strategy Pattern in Python that doesn’t involve making instances of a class or using functions as the strategy object.
The Spark
This idea came to me because of this article about “Virtues of a Good Object”. While I don’t agree very much with what he has said, his third point about objects being unique got me thinking. The Strategy Pattern is usually implemented with stateless objects and that’s a little bit of a bad thing, since creating more instances of that is pointless. It seems like the only good use for the Singleton Pattern. But Python, I realized, has an even better way.
Classes are Objects
In Python, classes are another kind of object, derived from metaclasses. You can pass classes around just like you can pass object and functions. Heck, you can even pass metaclasses around. And you can call the class methods and static methods on a class just like you can call normal methods on an object. So that gave me an idea. If a Strategy can be implemented without requiring any state, it should be done so as an uninstantiable class.
Here’s an example of such a Strategy class.
class Strategy: @classmethod def run(cls): print("Strategy was run")
The example could have used @staticmethod
instead of @classmethod
, too, allowing you to remove the cls
parameter.
Aside: Strategy Pattern in Python
This is a terrible example, though, since it only uses one method, and a stateless one at that. If a method/function accepts a Strategy that only has one method to call, it should not be required to be a method.
For (a terrible, oversimplified) example, if you have a method/function like this:
def strategy_user(strategy): strategy.run()
You are required to pass an object that has a run()
method. But why do we need to force that? If there’s only one method being called, it might as well be the __call__()
method that is run implicitly.
So, you’d remake the previous snippet into
def strategy_user(strategy): strategy()
Now, instead of requiring a class to follow a certain protocol, you need only accept a callable, which could be an object that implements __call__()
, a function, or a reference to a method. You can use any one of these options:
def strategy_one(): print("strategy_one called") class Strategy_Two: def __call__(self): print("Strategy_Two called") class Strategy_Three: def run(self): print("Strategy_Three called") # our Strategy class from earlier using a static method instead class Strategy_Four: @staticmethod def run(): print("Strategy_Four called") # using each of the Strategy types strategy_user(strategy_one) strategy_user(Strategy_Two()) strategy_user(Strategy_Three().run) strategy_user(Strategy_Four.run)
Obviously, you can replace Strategy_Two()
and Strategy_Three()
with already-created instances of those classes.
Back to Strategy Classes
So, what’s the point of the strategy class when a function works just fine? The point is that it can implement multiple methods instead of just one. If a strategy definition requires more than one step of strategy, then you either have to accept multiple functions in or a strategy object with multiple methods. The second way is usually easier.
The greatest advantage of using a strategy class instead of strategy object is that you will only ever have and need one instance, which is the class itself. You don’t need to make instance objects from the class.
Again, though, you only should use strategy classes if the strategy stores no state. Otherwise, you’re creating global state which can be altered in a different location unknowingly, changing the meaning of the strategy when you don’t want it to.
Notes On Design
If you want to enforce the idea of the only instance being the class itself, you can give it an __init__()
method that simply raises some sort of exception. I haven’t put a lot of thought into is, and I’m not sure if any of the built-in exceptions are good for this.
Prefer static methods over class methods, since it makes it “harder” to access class attributes for potential mutation, since class methods can use cls
instead of the actual class name. It simply discourages using the idea for situation that it isn’t meant for.
Other Languages
For other languages that can’t use classes as if they’re objects the way Python can, you can simply use the Singleton Pattern on the class, as mentioned earlier.
Outro
That’s my new idea. If it’s not so new, someone tell me, please.
What do you think? Does it have merit? Or does it break from the norms of OO enough that it should generally be ignored instead?
Reference: | Strategy Pattern Sans Objects and Functions from our WCG partner Jacob Zimmerman at the Programming Ideas With Jake blog. |