When dealing with multiple types which
are interacting, a program can get particularly messy. For example, consider a
system that parses and executes mathematical expressions. You want to be able to
say Number + Number, Number * Number, etc., where Number is
the base class for a family of numerical objects. But when you say a + b,
and you dont know the exact type of either a or b, so how
can you get them to interact properly?
Add Comment
The answer starts with something you
probably dont think about: Python performs only single dispatching. That
is, if you are performing an operation on more than one object whose type is
unknown, Python can invoke the dynamic binding mechanism on only one of those
types. This doesnt solve the problem, so you end up detecting some types
manually and effectively producing your own dynamic binding behavior.
Add Comment
The solution is called
multiple dispatching.
Remember that polymorphism can occur only via member function calls, so if you
want double dispatching to occur, there must be two member function calls: the
first to determine the first unknown type, and the second to determine the
second unknown type. With multiple dispatching, you must have a polymorphic
method call to determine each of the types. Generally, youll set up a
configuration such that a single member function call produces more than one
dynamic member function call and thus determines more than one type in the
process. To get this effect, you need to work with more than one polymorphic
method call: youll need one call for each dispatch. The methods in the
following example are called compete( ) and eval( ), and
are both members of the same type. (In this case there will be only two
dispatches, which is referred to as
double dispatching). If you
are working with two different type hierarchies that are interacting, then
youll have to have a polymorphic method call in each hierarchy.
Add Comment
Heres an example of multiple
dispatching:
#: c11:PaperScissorsRock.py
# Demonstration of multiple dispatching.
from __future__ import generators
import random
# An enumeration type:
class Outcome:
def __init__(self, value, name):
self.value = value
self.name = name
def __str__(self): return self.name
def __eq__(self, other):
return self.value == other.value
Outcome.WIN = Outcome(0, "win")
Outcome.LOSE = Outcome(1, "lose")
Outcome.DRAW = Outcome(2, "draw")
class Item(object):
def __str__(self):
return self.__class__.__name__
class Paper(Item):
def compete(self, item):
# First dispatch: self was Paper
return item.evalPaper(self)
def evalPaper(self, item):
# Item was Paper, we're in Paper
return Outcome.DRAW
def evalScissors(self, item):
# Item was Scissors, we're in Paper
return Outcome.WIN
def evalRock(self, item):
# Item was Rock, we're in Paper
return Outcome.LOSE
class Scissors(Item):
def compete(self, item):
# First dispatch: self was Scissors
return item.evalScissors(self)
def evalPaper(self, item):
# Item was Paper, we're in Scissors
return Outcome.LOSE
def evalScissors(self, item):
# Item was Scissors, we're in Scissors
return Outcome.DRAW
def evalRock(self, item):
# Item was Rock, we're in Scissors
return Outcome.WIN
class Rock(Item):
def compete(self, item):
# First dispatch: self was Rock
return item.evalRock(self)
def evalPaper(self, item):
# Item was Paper, we're in Rock
return Outcome.WIN
def evalScissors(self, item):
# Item was Scissors, we're in Rock
return Outcome.LOSE
def evalRock(self, item):
# Item was Rock, we're in Rock
return Outcome.DRAW
def match(item1, item2):
print "%s <--> %s : %s" % (
item1, item2, item1.compete(item2))
# Generate the items:
def itemPairGen(n):
# Create a list of instances of all Items:
Items = Item.__subclasses__()
for i in range(n):
yield (random.choice(Items)(),
random.choice(Items)())
for item1, item2 in itemPairGen(20):
match(item1, item2)
#:~This was a fairly literal
translation from the Java version, and one of the things you might notice is
that the information about the various combinations is encoded into each type of
Item. It actually ends up being a kind of table, except that it is spread
out through all the classes. This is not very easy to maintain if you ever
expect to modify the behavior or to add a new Item class. Instead, it can
be more sensible to make the table explicit, like this:
Add Comment
#: c11:PaperScissorsRock2.py
# Multiple dispatching using a table
from __future__ import generators
import random
class Outcome:
def __init__(self, value, name):
self.value = value
self.name = name
def __str__(self): return self.name
def __eq__(self, other):
return self.value == other.value
Outcome.WIN = Outcome(0, "win")
Outcome.LOSE = Outcome(1, "lose")
Outcome.DRAW = Outcome(2, "draw")
class Item(object):
def compete(self, item):
# Use a tuple for table lookup:
return outcome[self.__class__, item.__class__]
def __str__(self):
return self.__class__.__name__
class Paper(Item): pass
class Scissors(Item): pass
class Rock(Item): pass
outcome = {
(Paper, Rock): Outcome.WIN,
(Paper, Scissors): Outcome.LOSE,
(Paper, Paper): Outcome.DRAW,
(Scissors, Paper): Outcome.WIN,
(Scissors, Rock): Outcome.LOSE,
(Scissors, Scissors): Outcome.DRAW,
(Rock, Scissors): Outcome.WIN,
(Rock, Paper): Outcome.LOSE,
(Rock, Rock): Outcome.DRAW,
}
def match(item1, item2):
print "%s <--> %s : %s" % (
item1, item2, item1.compete(item2))
# Generate the items:
def itemPairGen(n):
# Create a list of instances of all Items:
Items = Item.__subclasses__()
for i in range(n):
yield (random.choice(Items)(),
random.choice(Items)())
for item1, item2 in itemPairGen(20):
match(item1, item2)
#:~Its a tribute to the
flexibility of dictionaries that a tuple can be used as a key just as easily as
a single object.
Add Comment
The assumption is that you have a primary
class hierarchy that is fixed; perhaps its from another vendor and you
cant make changes to that hierarchy. However, youd like to add new
polymorphic methods to that hierarchy, which means that normally youd
have to add something to the base class interface. So the dilemma is that you
need to add methods to the base class, but you cant touch the base class.
How do you get around this?
Add Comment
The design pattern that solves this kind
of problem is called a visitor (the final one in the Design
Patterns book), and it builds on the double dispatching scheme shown
in the last section.
Add Comment
The
visitor pattern allows you to extend
the interface of the primary type by creating a separate class hierarchy of type
Visitor to virtualize the operations performed upon the primary type. The
objects of the primary type simply accept the visitor, then call
the visitors dynamically-bound member function.
Add Comment
#: c11:FlowerVisitors.py
# Demonstration of "visitor" pattern.
from __future__ import generators
import random
# The Flower hierarchy cannot be changed:
class Flower(object):
def accept(self, visitor):
visitor.visit(self)
def pollinate(self, pollinator):
print self, "pollinated by", pollinator
def eat(self, eater):
print self, "eaten by", eater
def __str__(self):
return self.__class__.__name__
class Gladiolus(Flower): pass
class Runuculus(Flower): pass
class Chrysanthemum(Flower): pass
class Visitor:
def __str__(self):
return self.__class__.__name__
class Bug(Visitor): pass
class Pollinator(Bug): pass
class Predator(Bug): pass
# Add the ability to do "Bee" activities:
class Bee(Pollinator):
def visit(self, flower):
flower.pollinate(self)
# Add the ability to do "Fly" activities:
class Fly(Pollinator):
def visit(self, flower):
flower.pollinate(self)
# Add the ability to do "Worm" activities:
class Worm(Predator):
def visit(self, flower):
flower.eat(self)
def flowerGen(n):
flwrs = Flower.__subclasses__()
for i in range(n):
yield random.choice(flwrs)()
# It's almost as if I had a method to Perform
# various "Bug" operations on all Flowers:
bee = Bee()
fly = Fly()
worm = Worm()
for flower in flowerGen(10):
flower.accept(bee)
flower.accept(fly)
flower.accept(worm)
#:~