HiveBrain v1.2.0
Get Started
← Back to all entries
principlepythonMajor

Strategy design pattern with various duck type classes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
duckwithdesigntypeclassesstrategyvariouspattern

Problem

I've recently picked up the Head First Design Patterns book in an effort to become a more efficient and better Python programmer. Unfortunately, the code examples in this book are in Java.

I'm not the first one that wishes there was a Python version of this book, but I thought that writing the Python code would be a great exercise.

I've tried to implement the Strategy design pattern from Chapter 1 of Head First Design Patterns below in Python. Now, I know that Python is not Java in a general sense, but not in the nitty-gritty sense. So, I'm sure there are more Pythonic things that I can do to this code.

Off the top of my head we could probably implement the Duck class set_fly_behavior() and set_quack_behavior() using the Python property built-in.

Any other suggestions?

```
#!/usr/bin/env python

################################################################################
# Abstract Duck class and concrete Duck type classes.
################################################################################
class Duck(object):
def __init__(self):
pass

def set_fly_behavior(self, fb):
self.fly_behavior = fb

def set_quack_behavior(self, qb):
self.quack_behavior = qb

def perform_fly(self):
self.fly_behavior.fly()

def perform_quack(self):
self.quack_behavior.quack()

def swim(self):
print "All ducks float, even decoys!"

class MallardDuck(Duck):
def __init__(self):
Duck.__init__(self)
fly_instance = Fly()
self.set_fly_behavior(fly_instance)
quack_instance = Quack()
self.set_quack_behavior(quack_instance)
print "I'm a real Mallard Duck"

class ModelDuck(Duck):
def __init__(self):
Duck.__init__(self)
fly_no_way_instance = FlyNoWay()
self.set_fly_behavior(fly_no_way_instance)
squeak_instance = SqueakQuack()
self.set_quack_behavior(squeak_instance)
print "I'm a Model Duck"

class De

Solution

The devil’s in the _details

Your suggestion is good, but you don’t need a property; you can just use a normal attribute. All your setter does is set the variable, so just do that instead. So, this:

self.set_fly_behavior(fly_instance)


Can become this:

self._fly_behaviour = fly_instance


Note my use of self._fly_behaviour. Since it’s is an implementation detail of the class, the best practice is to prefix the variable name with an underscore. This tells programmers that it’s not designed to be used externally.

Aside: naming behaviours

The names perform_quack() and perform_fly() are very un-Pythonic. Why not just quack() and fly()? The perform tells us nothing—except, maybe leaking an implementation detail to the outside (that these delegated). This isn't something a caller needs to care about.

ABCs

Other than that, you could make your base classes Abstract Base Classes:

from abc import ABCMeta, abstractmethod

...

class QuackBehavior(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def quack(self):
        pass

    @classmethod
    def __subclasshook__(cls, Check):
        required = ["quack"]
        rtn = True
        for r in required:
          if not any(r in vars(BaseClass) for BaseClass in Check.__mro__):
            rtn = NotImplemented
        return rtn


This allows you to have a more Pythonic interface for your class.

(Also, note the removal of the empty constructor. Python provides an empty constructor anyway, so don’t bother defining one if you do nothing with it. It’s just useless extra code.)

A functional approach

This is a pretty heavyweight solution for Python. If you know that these behaviours are always just one function, I would argue classes aren’t needed.

Instead, recall that Python functions are first-class objects:

def quack(self):
    print("Quack")

def mute_quack(self):
    print ">"

some_duck = Duck()
some_duck.quack = mute_quack


A hybrid approach

We can make this work with one small modification: by using a property to convert functions into methods, so that they can be called normally.

(I’m assuming you need access to self in the method; if not, leave out that argument, and it’ll work as-is, like a static method.)

import types

class Duck(object):
    ...

    @property
    def fly(self):
        return self._fly

    @fly.setter
    def fly(self, value):
        self._fly = types.MethodType(value, self)

    @property
    def quack(self):
        return self._quack

    @quack.setter
    def quack(self, value):
        self._quack = types.MethodType(value, self)


One step further...

Hmm...we’re doing the same thing for both properties. If you want, we can take it a step further.

Let’s generalise it instead:

import types

def method_property(name):
    def getter(self):
        return getattr(self, name)
    def setter(self, value):
        setattr(self, name, types.MethodType(value, self))
    return getter, setter

class Duck(object):
    ...
    quack = property(*method_property("_quack"))
    fly = property(*method_property("_fly"))


Just assign the function as needed—perhaps in the constructor of subclasses. Obviously, if you need more complicated behaviours, this might not do. But for simple things, I’d say it’s the better solution.

Constructors for subclassing

Since a duck is useless without a quack and fly behaviour, and all subclasses assign them, it makes sense for the constructor to deal with this:

class Duck(object):
    def __init__(self, fly, quack):
        self.fly = fly
        self.quack = quack

    ...

class ModelDuck(Duck):
    def __init__(self):
        Duck.__init__(self, fly_no_way, squeak_quack)
        print "I'm a Model Duck"


Or, if you were using your original class’s implementation:

class Duck(object):
    def __init__(self, fly, quack):
        self._fly_behaviour = fly
        self._quack_behaviour = quack

    ...

class ModelDuck(Duck):
    def __init__(self):
        Duck.__init__(self, FlyNoWay(), SqueakQuack())
        print "I'm a Model Duck"


Conclusion

If we wrap all this together, we end up with two implementations:

  • function based, and



  • class based.



These implementations remove repetition, and will make this sort of thing much easier to work with in Python overall.

Code Snippets

self.set_fly_behavior(fly_instance)
self._fly_behaviour = fly_instance
from abc import ABCMeta, abstractmethod

...

class QuackBehavior(object):
    __metaclass__ = ABCMeta

    @abstractmethod
    def quack(self):
        pass

    @classmethod
    def __subclasshook__(cls, Check):
        required = ["quack"]
        rtn = True
        for r in required:
          if not any(r in vars(BaseClass) for BaseClass in Check.__mro__):
            rtn = NotImplemented
        return rtn
def quack(self):
    print("Quack")

def mute_quack(self):
    print "<< Silence >>"

some_duck = Duck()
some_duck.quack = mute_quack
import types

class Duck(object):
    ...

    @property
    def fly(self):
        return self._fly

    @fly.setter
    def fly(self, value):
        self._fly = types.MethodType(value, self)

    @property
    def quack(self):
        return self._quack

    @quack.setter
    def quack(self, value):
        self._quack = types.MethodType(value, self)

Context

StackExchange Code Review Q#20718, answer score: 32

Revisions (0)

No revisions yet.