24
\$\begingroup\$

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 DecoyDuck(Duck):
    def __init__(self):
        Duck.__init__(self)
        fly_no_way_instance = FlyNoWay()
        self.set_fly_behavior(fly_no_way_instance)
        mute_instance = MuteQuack()
        self.set_quack_behavior(mute_instance)
        print "I'm a Decoy Duck"

################################################################################
# Fly behavior interface and behavior implementation classes.
################################################################################
class FlyBehavior():
    def __init__(self):
        pass

class Fly(FlyBehavior):
    def fly(self):
        print "I'm flying!!"

class FlyNoWay(FlyBehavior):
    def fly(self):
        print "I can't fly"

class FlyRocketPowered(FlyBehavior):
    def fly(self):
        print "I'm flying with a rocket!"

################################################################################
# Quack behavior interface and behavior implementation classes.
################################################################################
class QuackBehavior():
    def __init__(self):
        pass

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

class MuteQuack(QuackBehavior):
    def quack(self):
        print "<< Silence >>"

class SqueakQuack(QuackBehavior):
    def quack(self):
        print "Squeak"

################################################################################
# Test Code.
################################################################################
if __name__ == "__main__":

    print '#'*80
    print "Mallard Duck"
    print '#'*80
    mallard = MallardDuck()
    mallard.perform_quack()
    mallard.perform_fly()

    print '#'*80
    print "Model Duck"
    print '#'*80
    model = ModelDuck()
    model.perform_fly()
    fly_rocket_powered_instance = FlyRocketPowered()
    model.set_fly_behavior(fly_rocket_powered_instance)
    model.perform_fly()

    print '#'*80
    print "Decoy Duck"
    print '#'*80
    decoy = DecoyDuck()
    decoy.perform_fly()
    decoy.perform_quack()
\$\endgroup\$
0

1 Answer 1

32
\$\begingroup\$

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 "<< Silence >>"

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:

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

\$\endgroup\$
8
  • 2
    \$\begingroup\$ Wow, thanks for such a detailed reply. I'll be sure to +1 you once I get enough rep. In the meantime you've given me quite a bit of material to look over and apply to my programming. I'm also going to continue working on more design patterns from the Head First book. Please be on the lookout :) \$\endgroup\$
    – PythonJin
    Jan 19, 2013 at 23:01
  • \$\begingroup\$ I'm still groking some of the code here. For the class based implementation, I'm trying to get a handle on the __subclasshook__. In our abstract behaviors we define the builtin __subclasshook__ to be the classmethod of the function subclasshook, which is defined at the top of the code. So the classmethod builtin should return the Class of the function subclasshook. But I thought __subclasshook__ was only suppose to return True, False, or NotImplemented. I'm certainly missing something, since the code works. \$\endgroup\$
    – PythonJin
    Jan 20, 2013 at 12:37
  • \$\begingroup\$ Also, I'm not quite following the logic here in the for loop of the function subclasshook(requried) at the top. My problem is with the line if not any(r in vars(BaseClass) for BaseClass in Check.__mro__):. The variable r is the name of the abstract method that needs to be implemented in each concrete behavior. So here we check the __mro__ of the concrete class and all of its base classes(?) for the name of the abstract method that must be implemented. Shouldn't we check to see that the method is in every class of the __mro__, not just any class? \$\endgroup\$
    – PythonJin
    Jan 20, 2013 at 12:52
  • 1
    \$\begingroup\$ @PythonJin In response to your first comment, subclasshook() returns a function, so when we call __subclasshook__ = classmethod(subclasshook(["fly"])), Python evaluates the subclasshook() call, and gives classmethod() the function that returns True, False, or NotImplemented as the first argument. It does not pass classmethod() the subclasshook() function itself, just the value returned by it (which happens to be another function). It then makes it a class method and assigns it to __subclasshook__. \$\endgroup\$
    – Latty
    Jan 20, 2013 at 16:11
  • \$\begingroup\$ @PythonJin In response to comment two, think about how Python works when you call a function. It looks for the function you call in that class, and if it doesn't find it, works up the MRO (which is all of the base classes, with duplicates removed in a certain order - as Python support multiple inheritance this isn't trivial) and checks for it until it finds it. If we checked if it was in all classes, then it would be impossible to add functionality into a subclass without it being in the base class. \$\endgroup\$
    – Latty
    Jan 20, 2013 at 16:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.