views:

675

answers:

3

In the Wikipedia entry for the Strategy Pattern:

http://en.wikipedia.org/wiki/Strategy_pattern

Most other code sample is doing something like:

a = Context.new(StrategyA.new)
a.execute #=> Doing the task the normal way

b = Context.new(StrategyB.new)
b.execute #=> Doing the task alternatively

c = Context.new(StrategyC.new)
c.execute #=> Doing the task even more alternative

Right now the Python code is doing something else, with a Submit button. I wonder how the Python code will look like if it is also doing what other code sample is doing?

Update: can it be shorter using the first-class function in Python?

+6  A: 

The example in Python is not so different of the others. To mock the PHP script :

class StrategyExample :

    def __init__(self, func=None) :
        if func :
             self.execute = func

    def execute(self) :
        print "Original execution"


def executeReplacement1() :
        print "Strategy 1"


def executeReplacement2() :
         print "Strategy 2"

if __name__ == "__main__" :

    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat2 = StrategyExample(executeReplacement2)

    strat0.execute()
    strat1.execute()
    strat2.execute()

This outputs :

Original execution
Strategy 1
Strategy 2

The main differences are :

  • You don't need to write any other class nor implementing any interface.
  • Instead you can pass a function reference that will be binded to the method you want.
  • So the functions can still be used stand alone, and the original object can have a default behavior if you want to (the if func == None can be used for that).
  • Indeed, it's clean short and elegant as usual with Python. But you loose information : : no explicit interface, so the programmer is assumed as an adult knowing what is doing.

Note that there is 3 ways to dynamically add a method in Python :

  • the way I've shown you. But the method will be static, it won't get the "self" argument passed.

  • using the class name :

    StrategyExample.execute = func

Here, all the instance will get "func" as the "execute" method, that will get "self" passed as an argument.

  • binding to an instance only (using types) :

    strat0.execute = types.MethodType(executeReplacement1, strat0, StrategyExample)

This will bind the new method to "start0", and only "strat0" like with the first example. But start0.execute() will get "self" passed as an argument.

If you need to use a reference to the current instance in the function, then you will combine the first and the last method. If you do not :

class StrategyExample :

    def __init__(self, func=None) :
        self.name = "Strategy Example 0"
        if func :
             self.execute = func

    def execute(self) :
        print self.name


def executeReplacement1() :
        print self.name + " from execute 1"


def executeReplacement2() :
         print self.name + " from execute 2"

if __name__ == "__main__" :

    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat1.name = "Strategy Example 1"
    strat2 = StrategyExample(executeReplacement2)
    strat2.name = "Strategy Example 2"

    strat0.execute()
    strat1.execute()
    strat2.execute()

You will get :

Traceback (most recent call last):
  File "test.py", line 28, in <module>
    strat1.execute()
  File "test.py", line 13, in executeReplacement1
    print self.name + " from execute 1"
NameError: global name 'self' is not defined

So the proper code would be :

import types

class StrategyExample :

    def __init__(self, func=None) :
        self.name = "Strategy Example 0"
        if func :
             self.execute = types.MethodType(func, self, StrategyExample)

    def execute(self) :
        print self.name


def executeReplacement1(self) :
        print self.name + " from execute 1"


def executeReplacement2(self) :
         print self.name + " from execute 2"

if __name__ == "__main__" :

    strat0 = StrategyExample()
    strat1 = StrategyExample(executeReplacement1)
    strat1.name = "Strategy Example 1"
    strat2 = StrategyExample(executeReplacement2)
    strat2.name = "Strategy Example 2"

    strat0.execute()
    strat1.execute()
    strat2.execute()

This will output the expected result :

Strategy Example 0
Strategy Example 1 from execute 1
Strategy Example 2 from execute 2

Of course in the case the functions cannot be used stand alone anymore, but still can binded to any other instance of any object, without any interface limitation.

e-satis
+2  A: 

For clarity, I would still use a pseudo-interface:

class CommunicationStrategy(object):
    def execute(self, a, b):
        raise NotImplementedError('execute')

class ConcreteCommunicationStrategyDuck(CommunicationStrategy):
    def execute(self, a, b):
        print "Quack Quack"

class ConcreteCommunicationStrategyCow(CommunicationStrategy):
    def execute(self, a, b):
        print "Mooo"

class ConcreteCommunicationStrategyFrog(CommunicationStrategy):
    def execute(self, a, b):
        print "Ribbit! Ribbit!"
NicDumZ
+4  A: 

You're right, the wikipedia example isn't helpful. It conflates two things.

  1. Strategy.

  2. Features of Python that simplify the implementation of Strategy. The "there's no need to implement this pattern explicitly" statement is incorrect. You often need to implement Strategy, but Python simplifies this by allowing to you use a function without the overhead of a class wrapper around a function.

First, Strategy.

class AUsefulThing( object ):
    def __init__( self, aStrategicAlternative ):
        self.howToDoX = aStrategicAlternative
    def doX( self, someArg ):
        self. howToDoX.theAPImethod( someArg, self )

class StrategicAlternative( object ):
    pass

class AlternativeOne( StrategicAlternative ):
    def theAPIMethod( self, someArg, theUsefulThing ):
        pass # an implementation

class AlternativeTwo( StrategicAlternative ):
    def theAPImethod( self, someArg, theUsefulThing ):
        pass # another implementation

Now you can do things like this.

t = AUsefulThing( AlternativeOne() )
t.doX( arg )

And it will use the strategy object we created.

Second, Python alternatives.

class AUsefulThing( object ):
    def __init__( self, aStrategyFunction ):
        self.howToDoX = aStrategyFunction
    def doX( self, someArg ):
        self.howToDoX( someArg, self )

def strategyFunctionOne( someArg, theUsefulThing ):
        pass # an implementation

def strategyFunctionOne( someArg, theUsefulThing ):
        pass # another implementation

We can do this.

t= AUsefulThing( strategyFunctionOne )
t.doX( anArg )

This will also use a strategy function we provided.

S.Lott