Strategy pattern in Pyjamas

I was recently trying to implement a Strategy pattern inside my Pyjamas web app. It’s basically a design pattern that allows the programmer to use multiple algorithms interchangeably without relying on inheritance, and it’s meant for cases when inheritance would add too much complexity.

For example, imagine that we’re implementing a class for a video game character, which has an attack() method. This method would probably vary depending on the weapon the character is holding. If the character is bare-handed he will probably punch the opponent. If the character is holding a sword, he’ll swing the sword at the opponent instead. If the character is holding a spear, he’ll perform a throwing motion. In this case, inheritance is clearly a poor choice, we’re not going to morph the character into a new object each time he switches a weapon. One other way to implement this is by using if/elif/else statements, but this solution isn’t much better, since we’ll end up with a behemoth method spanning several hundred lines of code as soon as we have a decent arsenal of weapons. We could also implement if/elif/else setup that calls other methods, but even then we’re adding unnecessary overhead. The cleanest solution is a Strategy pattern, which essentially maps the correct method at run-time.

Unfortunately, Pyjamas doesn’t seem to support either the types module or get() method for binding a function/method to a class method. Without it, Strategy pattern seems impossible to implement in Python.

For example, using types module, a clean way to implement this pattern in Python would be as follows:

import types

def strategyA(possible_self):
...

instance = OrigObject()
instance.strategy = types.MethodType(strategyA, instance)
instance.strategy()


This runs fine in Pyjamas Desktop, but throws an import error in the browser since types module does not exist in current Pyjamas implementation:

ImportError: No module named types, types in context None


There is an internalast fork of Pyjamas that does implement the types module, but does not implement the MethodType function within it. Essentially, the only difference would be the type of error you get.

There is an alternative way to implement the Strategy pattern. Every function/method in Python has descriptor methods that allow you to customize when you use an attribute. One of such methods is get() which allows you to rebind the method as follows:

instance.strategy = strategyA.__get__(self, OrigObject)


Unfortunately this solution also doesn’t work, throwing a type error in Pyjamas (since descriptors aren’t yet implemented in Pyjamas):

TypeError: Object function ... has no method '__get__'



The main problem is that Pyjamas will not let you bind an unbound method to an instance in Pyjamas for use as a bound method later. Which brings us to a somewhat hackerish (is that a word?) workaround. The idea is to define all alternative methods inside the class itself, and then swap between them, as if using variables. For example, if we have two alternative algorithms, strategyA and strategyDefault, we can switch back and forth between them as follows or each instance of the class independently:

self.strategyDefault, self.strategyA = self.strategyA, self.strategyDefault

If you need more than 2 methods, you can still use the same approach, but instead of swapping 2 methods, set the active one to point to one of the alternatives, keeping a backup of the original in another variable.

I must admit that even despite being the language where “there should only be one obvious way to do it” Python allows the programmer great flexibility in terms of implementation. And that flexibility is one of Pyjamas’ best features, in that even if Pyjamas drops the ball, Python tends to have plenty of firepower to pick up the slack.

This entry was posted in Frameworks, Hacks and Tricks and tagged , by Alexander Tsepkov. Bookmark the permalink.

About Alexander Tsepkov

Founder and CEO of Pyjeon. He started out with C++, but switched to Python as his main programming language due to its clean syntax and productivity. He often uses other languages for his work as well, such as JavaScript, Perl, and RapydScript. His posts tend to cover user experience, design considerations, languages, web development, Linux environment, as well as challenges of running a start-up.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>