41

In the 2009 Wikipedia entry for the Strategy Pattern, there's a example written in PHP.

Most other code samples do 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

In the Python code a different technique is used with a Submit button. I wonder what the Python code will look like if it also did it the way the other code samples do.

Update: Can it be shorter using first-class functions in Python?

martineau
  • 99,260
  • 22
  • 139
  • 249
nonopolarity
  • 130,775
  • 117
  • 415
  • 675

5 Answers5

75

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()

Output:

Original execution
Strategy 1
Strategy 2

The main differences are:

  • You don't need to write any other class or implement any interface.
  • Instead you can pass a function reference that will be bound to the method you want.
  • The functions can still be used separately, and the original object can have a default behavior if you want to (the if func == None pattern can be used for that).
  • Indeed, it's clean short and elegant as usual with Python. But you lose information; with no explicit interface, the programmer is assumed as an adult to know what they are doing.

Note that there are 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, and will get self passed as an argument.

  • Binding to an instance only (using the types module):

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

    or with Python 2, the class of the instance being changed is also required:

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

This will bind the new method to strat0, 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 would 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 sys
import types

if sys.version_info[0] > 2:  # Python 3+
    create_bound_method = types.MethodType
else:
    def create_bound_method(func, obj):
        return types.MethodType(func, obj, obj.__class__)

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

    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 can still be bound to any other instance of any object, without any interface limitation.

martineau
  • 99,260
  • 22
  • 139
  • 249
e-satis
  • 515,820
  • 103
  • 283
  • 322
46

Answering an old question for the Googlers who searched "python strategy pattern" and landed here...

This pattern is practically non-existent in languages that support first class functions. You may want to consider taking advantage of this feature in Python:

def strategy_add(a, b):
    return a + b

def strategy_minus(a, b):
    return a - b

solver = strategy_add
print solver(1, 2)
solver = strategy_minus
print solver(2, 1)

This approach is very clean and simple.

Also, be sure to check out Joe Gregorio's PyCon 2009 talk about Python and design patterns (or lack thereof): http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth

Bono
  • 4,477
  • 6
  • 43
  • 76
Christian Groleau
  • 743
  • 1
  • 10
  • 17
  • 2
    Then why we have classes in python? you sometimes design patterns are helpful in huge projects, you can't get away from it :) – vivek May 19 '14 at 04:03
  • 3
    Working [link](http://pyvideo.org/video/146/pycon-2009--the--lack-of--design-patterns-in-pyth). In the other video is removed. – kon psych Mar 02 '15 at 01:51
  • 1
    @vivek One doesn't have classes in python or any other language specifically to facilitate design patterns. Many things are well represented by classes. However requiring classes to represent e.g. command or strategy pattern is a weakness. Any language with proper closures can do without. And huge projects is not a requirement for classes specifically. Lots of languages don't have classes at all and yet are used to create large projects. Of course classes are the only way to express data types in Python so you can't get away from them. – Erik Engheim Jan 13 '17 at 16:33
  • [Here](https://norvig.com/design-patterns/) is Peter Norvig's post on "Design Patterns in Dynamic Languages" that Joe Gregorio mentions in the video. – mcrisc Apr 01 '21 at 14:53
34

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 you to 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 strategyFunctionTwo( someArg, theUsefulThing ):
        pass # another implementation

We can do this.

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

This will also use a strategy function we provided.

Matthew Rankin
  • 400,554
  • 38
  • 116
  • 156
S.Lott
  • 359,791
  • 75
  • 487
  • 757
  • which one do you use? What would you recommend using? – Escualo Mar 20 '11 at 20:23
  • 6
    @Arrieta: Use Both. Recommend Both. They have different applications. If the *Strategy* has multiple methods or is (somehow) stateful, then you need the first. In the more typical case, the second works fine. If you're working with a lot of Java or C++ programmers you have to use the first because the second confuses them. – S.Lott Mar 21 '11 at 10:48
11

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!"
Nicolas Dumazet
  • 6,980
  • 23
  • 36
4

I've tried to convert the 'Duck' example from the 1st chapter (covering Strategy Pattern) of Head First Design Pattern in Python:

class FlyWithRocket():
    def __init__(self):
        pass
    def fly(self):
        print 'FLying with rocket'

class FlyWithWings():
    def __init__(self):
        pass
    def fly(self):
        print 'FLying with wings'

class CantFly():
    def __init__(self):
        pass
    def fly(self):
        print 'I Cant fly'

class SuperDuck:
    def __init__(self):
        pass
    def setFlyingBehaviour(self, fly_obj):
        self.fly_obj = fly_obj
    def perform_fly(self):
        self.fly_obj.fly()

if __name__ == '__main__':
    duck = SuperDuck()
    fly_behaviour = FlyWithRocket()
    #fly_behaviour = FlyWithWings()
    duck.setFlyingBehaviour(fly_behaviour)
    duck.perform_fly()
Saurabh Verma
  • 5,538
  • 9
  • 45
  • 75
  • It would be better to set flying behavior in constructor or at least put a default value in constructor. You might forget to set it and get error otherwise. – kon psych Jul 13 '16 at 20:45