Implementation Patterns: How to Express the Behavior of a Program

• Print
This chapter is from the book

Double Dispatch

Choosing messages are good for expressing a single dimension of variability. In the example in “Choosing Message,” this dimension was the type of medium on which the shape was to be drawn. If you need to express two independent dimensions of variability, you can cascade two choosing messages.

For example, suppose I wanted to express that a Postscript oval was computed differently than a screen rectangle. First I would decide where I wanted the computations to live. The base computations seeem like they belong in the Brush, so I will send a choosing message first to the Shape, then to the Brush:

```displayShape(Shape subject, Brush brush) {
shape.displayWith(brush);
}```

Now each Shape has the opportunity to implement displayWith() differently. Rather than do any detailed work, however, they append their type onto the message and defer to the Brush:

```Oval.displayWith(Brush brush) {
brush.displayOval(this);
}
Rectangle.displayWith(Brush brush) {
brush.displayRectangle(this);
}```

Now the different kinds of brushes have the information they need to do their work:

```PostscriptBrush.displayRectangle(Rectangle subject) {
writer print(subject.left() +" " +...+ " rect);
}```

Double dispatch introduces some duplication with a corresponding loss of flexibility. The type names of the receivers of the first choosing message get scattered over the methods in the receiver of the second choosing message. In this example, this means that to add a new Shape, I would have to add methods to all the Brushes. If one dimension is more likely to change than the other, make it the receiver of the second choosing message.

The computer scientist in me wants to generalize to triple, quadruple, quintuple dispatch. However, I’ve only ever attempted triple dispatch once and it didn’t stay for long. I have always found clearer ways to express multi-dimensional logic.