Strategy pattern

From wikinotes

The strategy pattern has a couple of very abstract goals:

  • facilitate maintenance/usage (do not worry about adding method to base-class)
  • sharing methods between subclasses at the same inheritance-level (without mixins)
  • compartmentalize reusable behaviours
  • simpler to test
  • less prone to cyclical imports :)


Problem

Say that you wanted to add the identical method self.fly() to both GrandChildA1() and GrandChildB1() only. Or, say that you wanted to add self.fly() to Parent() - but different subclasses would need different implementations.


                            Parent()
                               |
              +----------------+-------------------+
              |                                    |
           ChildA()                             ChildB()
              |                                    |
              |                                    |
     +--------+--------+                    +------+-------+
     |                 |                    |              |
GrandChildA1()   GrandChildA2()       GrandChildB1() GrandChildB2()

In the inheritance diagram above,

  • It is easy to introduce behaviour to Parent() that will break other classes
  • If you wanted to re-use code horizontally (ex: GrandChildA1() and GrandChildB1() only) you would need to create a confusing mixin class.

Solution

The Strategy Pattern uses parameters to solve the issue. Each IFlyBehaviour() has it's own method self.fly(). This object's fly() is called by the object.

                                      Parent()                      
                                         |                          
                                         |                          
                                         |                          
              +--------------------------+-------------------------+
              |                                                    |
           ChildA()                  FlyBehaviour()             ChildB()
              |                         NoFly()                    |
              |                         JetFly()                   |
     +--------+------------------+      WingFly()           +------+-----------------+                   
     |                           |                          |                        |                   
GrandChildA1(IFlyBehaviour.NoFly)   GrandChildA2()         GrandChildB1(IFlyBehaviour.NoFly) GrandChildB2()

This solution has some side-benefits as well:

  • easy to know all handled FlyBehaviours in all classes
  • do you still need ChildA/subclasses GrandChildA1/GrandChildB2? Can this be solved with a single class now?
  • This is Dependency Injection. Easier to test, less prone to cyclical imports.


Example:

# flybehaviours.py

class FlyBehaviour(object):
    """ Base-Class for FlyBehaviours
    """
    __metaclass__ = abc.ABCMeta
    def fly(self):
        raise NotImplementedError()


class NoFly(FlyBehaviour):
    def fly(self):
        return 'walking'
    
class JetFly(FlyBehaviour):
    def fly(self):
        return 'whooshing'
    
class WingFly(FlyBehaviour):
    def fly(self):
        return 'flapping'

parent = Parent(flybehaviours.NoFly())


References