Strategy Design Pattern

Discussion in 'Python' started by Daniel Santa Cruz, Apr 21, 2006.

  1. Hello all,

    I've been trying to go over my OO Patterns book, and I decided to try
    to implement them in Python this time around. I figured this would
    help me learn the language better.

    Well, I've gotten stuck with my first go at OO patterns with Python. I
    guess it goes without say that some of the stuff that are taken for
    granted in most of the books (ie. Interfaces, Abstract classes) don't
    really apply to Python per say, but the idea behind the patterns can be
    extracted out still. In the specific case of the Strategy pattern, I
    think the goal is to abstract out of the class an algorithm that can
    then be reused by some of the inherited classes. This way we don't
    repeat ourselves. Maybe I got this all wrong...

    I'm at a loss at how I can do this with Python, any pointers would be
    more than welcomed!

    To aid commenters... we can use the example used in "Head First Design
    Patterns". I've implemented this simple patter in Java and .NET... now
    in python. I can't draw UML here, so I'll try to pseudo talk what the
    example has:

    Abstract Base Class: Duck
    + FlyBehavior _fly
    + swim()
    + fly() -> calls _fly.fly()

    Interface: FlyBehavior
    + fly()

    Concrete Interface FlyHigh (implements FlyBehavior):
    + fly()

    Concrete Class Duck1 (Inherits Duck):
    + Constructor: _fly = new FlyHigh()

    Daniel.
     
    Daniel Santa Cruz, Apr 21, 2006
    #1
    1. Advertisements

  2. Check out my homepage, www.aleax.it, you'll find several PDFs for my
    various presentations over the years: many have to do with DPs and
    Python, and I specifically addressed Strategy in a couple of them.


    Alex
     
    Alex Martelli, Apr 21, 2006
    #2
    1. Advertisements

  3. Daniel  Santa Cruz

    Kay Schluehr Guest

    I guess it might help clarifying what OO is about since you see what
    happens when you try to transform solutions across languages with very
    different type systems.
    Well, yes, I disagree ;) From my point of view the StrategyPattern is
    used to decouple the consumer/client/user of an algorithm from it's
    implementation. If you need variations of the implementation of your
    algo you don't want variations in the user. It's not about a tower of
    abstractions in the Strategy itself. Those abstractions are there for
    the purpose of enabling polymorphism.

    If you want a straightforward translation you start with a Strategy
    class and derive strategies from it:

    class Strategy(object):
    def Evaluate(self):
    raise NotImplementedError # making it abstract

    class StrategyA(Strategy):
    def Evaluate(self):
    print "Evaluate StrategyA"

    class StrategyB(Strategy):
    def Evaluate(self):
    print "Evaluate StrategyB"

    This is very clean OO but you don't actually need the Strategy base
    class because Pythons type system does not enforce a well known type at
    compile time. In some sense also the StrategyX classes are overhead
    because you can pass the Evaluate methods directly to the consumer:
    functions are always first class objects. So the Strategy might be
    reduced to:

    def EvaluateA(self):
    print "Evaluate StrategyA"

    def EvaluateB(self):
    print "Evaluate StrategyB"

    def consumer(evaluate):
    evaluate()
    Evaluate StrategyB
     
    Kay Schluehr, Apr 21, 2006
    #3
  4. Daniel  Santa Cruz

    Ben Sizer Guest

    In the original "Design Patterns" book, the authors pointed out that
    although patterns are generally language-agnostic, the need for
    formalised patterns to achieve a given goal is not. One example is
    inheritance - if you're programming in C then you'd need a formal
    pattern to model inheritance effectively, but in many other languages
    it's built in. It may be the case that Python gives you the tools to
    make some other patterns effectively redundant.
    The goal is to allow an object to have a certain behaviour which uses
    an algorithm you can change by changing the object that performs that
    algorithm. It essentially means you can change the algorithm later
    without changing the object.

    In Python, this is quite simple - decide upon an interface that the
    algorithm should provide, and then assign an object that implements
    that interface to your object. The object will then pass its data to
    the interface methods on whichever algorithm object it's been given.
    In this example, you can just assign a FlyHigh function to your Duck1 -
    no Duck or FlyBehavior classes needed. The pattern is almost pointless
    in situations like this when your language allows you to quickly and
    easily assign functions between objects. However if, as is common, you
    need more than 1 function in the interface, you can put them in an
    object and assign that.

    class FlyHigh(object):
    def TakeOff(self, bird):
    print "take off to fly high"
    def Land(self, bird):
    print "land from flying high"

    class FlyLow(object):
    def TakeOff(self, bird):
    print "take off to fly low"
    def Land(self, bird):
    print "land from flying low"

    class Duck(object):
    def __init__(self, flyingBehavior):
    self.flyBehavior = flyingBehavior
    def TakeOff(self):
    self.flyBehavior.TakeOff(self)
    def Land(self):
    self.flyBehavior.Land(self)

    lowFlyingDuck = Duck(FlyLow())
    highFlyingDuck = Duck(FlyHigh())


    Note that I don't claim the above code is necessarily optimal (or even
    correct!) but it should show that half of the Strategy pattern
    boilerplate is unnecessary in Python. You can even use certain Python
    tricks to automatically delegate calls on Duck to the behavior classes
    without typing those out individually, for example.
     
    Ben Sizer, Apr 21, 2006
    #4

  5. This is my understanding of the Strategy Pattern:

    (Perhaps I have a SudokuSolver class that will solve *any* Sudoku
    puzzle, but it's very slow for simple puzzles because of some
    unavoidable overhead related only to the more difficult puzzles - so I
    want to try a simpler but faster method first, if it doesn't succeed
    then use the more complex method.)

    class SudokuGrid(object):
    def __init__(self, solver=None):
    self.grid = [[-1]*9]*9
    if solver is not None:
    self.solver = solver
    else:
    self.solver = SimpleSudokuSolver()

    def solve(self):
    self.solver.sudoku = self
    self.solver.solve()

    class SimpleSudokuSolver(object):
    sudoku = None
    def solve(self):
    print 'solving %s ...simple algorithm...' % repr(self.sudoku)

    class DifficultSudokuSolver(object):
    sudoku = None
    def solve(self):
    print 'solving %s ...difficult algorithm...' %
    repr(self.sudoku)

    s = SudokuGrid()

    s.solve()

    s.solver = DifficultSudokuSolver()

    s.solve()

    solving <__main__.SudokuGrid object at 0x0117D390> ...simple
    algorithm...
    solving <__main__.SudokuGrid object at 0x0117D390> ...difficult
    algorithm...

    HTH

    Gerard
     
    Gerard Flanagan, Apr 21, 2006
    #5
  6. A good part of the GoF patterns are meant to add flexibility to static
    languages - translating them directly in a dynamic language may not be
    such a good idea. Regarding abstract base classes (Java's interfaces
    being a special case of abc), they are mostly used in static languages
    to provide support for polymorphic dispatch. This is not needed in
    Python, where polymorphic dispatch is not tied to inheritance.

    FWIW, it's still possible to have abc in Python:

    class Abc(object):
    def abstractMethod(self, args):
    raise NotImplementedError("Abc is an abstract class")
    The first part (abstract an algorithm out of the class) is ok. The real
    goal is to allow to dynamically select the algorithm to use based on
    runtime conditions (ie: user prefs, platform-specific stuff, size of a
    file, phase of the moon, whatnot...).
    Hint 1: Python's functions are objects
    Hint 2: Dynamically attaching a method to an object (ie : not to the
    whole class) is easy as pie (search this group...).
    Hint 3: Any object having a __call__() method is callable.
     
    bruno at modulix, Apr 21, 2006
    #6
  7. IMHO yes. The goal isn't to reuse the algorithm elsewhere but to plug in
    different algorithms into the "main class", which is called `context` in
    the GoF book.
    No need to make a subclass of `Duck` and the base class should not be
    abstract but take a strategy in the constructor instead.

    Silly, contrived example with ducks (the `context`):

    class Duck(object):
    def __init__(self, fly_strategy=lambda duck: None):
    self._fly = fly_strategy
    self.battery = 100.0 # Percent.

    def fly(self):
    self._fly(self)

    A dumb strategy:

    def flap_as_fast_as_you_can(duck):
    duck.battery -= 5

    And a more sophisticated one:

    def ExploitThermodynamics(object):
    def __init__(self):
    # Initialize data structures to keep track of wind, target etc.

    def __call__(self, duck):
    # Check wind, update data and change the heading of the duck
    # accordingly.
    duck.battery -= used_power

    Now some ducks:

    no_fly_duck = Duck()
    fast_but_soon_tired_duck = Duck(flap_as_fast_as_you_can)
    gliding_duck = Duck(ExploitThermodynamics())

    If the `strategy` is a callable one can decide if a simple function is
    sufficient or if the `strategy` needs some state that must be preserved
    between calls to the strategy object.

    Ciao,
    Marc 'BlackJack' Rintsch
     
    Marc 'BlackJack' Rintsch, Apr 21, 2006
    #7
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.