A Case of Dynamic Dispatching

In my previous post, I (intentionally) omitted various important technical and theoretical details. Here, I look at the concept of Dynamic Dispatching, and how it fits with the simple prettyToString() example.

Dynamic dispatching is what makes polymorphic behavior happen. When you call toString() on an object, the right implementation is called because Java dispatches to the appropriate method based on the type of the instance. But Java doesn’t go the whole length when it comes to dynamic dispatch: while it pays attention to the actual type of the instance, it only concerns itself with the type of the reference to any parameters, not their actual types. For example:

abstract class PrettyPrinter {
  abstract void prettyPrint(Number n);
  abstract void prettyPrint(Long l);
  abstract void prettyPrint(Double d);
}

final class AmericanPrettyPrinter extends PrettyPrinter { ... }

final class ItalianPrettyPrinter extends PrettyPrinter { ... }

PrettyPrinter printer = new AmericanPrettyPrinter();
Double d = new Double(34.5);
Number n = d;
printer.prettyPrint(d); // uses AmericanPrettyPrinter.prettyPrint(Double d);
printer.prettyPrint(n); // uses AmericanPrettyPrinter.prettyPrint(Number n);

Yes, this is painfully obvious. I am trying to highlight the asymmetry: while the instance’s type is considered at runtime but the parameter’s type is not. Some languages do not have this limitation; Java does.

The previous post’s if-elseif- sequence is one way of accommodating this limitation. Here’s a more complete list of approaches one might employ:

1. If the method in question doesn’t require parameters, you can simply use the dynamic dispatching supported by Java. There are two reasons to not do so. First, you may not be the author of the classes. In the above example, you cannot add public abstract String toPrettyString() to java.lang.Number, nor can you add concrete implementations of the method to java.lang.Double and java.lang.Long. Second, you may choose to not add the method “family” for coupling & cohesion reasons: you may not want to add various disparate methods like toPrettyString(), toDebugString(), toXmlString() etc., because your core classes’ code gets polluted with a plethora of specialized logic.

2. Use traditional flow control constructs like if-else or switch-case. When there are very few code path alternatives, this solution — however distasteful to purists — is often suitable.

3. Use a double dispatch idiom. The idiom looks like this:

interface ContactInfoVisitor {
  void handlePhysicalAddress(PhysicalAddress pa);
  void handlePhoneNumber(PhoneNumber pn);
  void handleEmailAddress(EmailAddress ea);
}

abstract class ContactInfo {
  abstract void visit(ContactInfoVisitor v);
}

class PhysicalAddress extends ContactInfo {
  void visit(ContactInfoVisitor v) {
    v.handlePhysicalAddress(this);
  }
}

class PhoneNumber extends ContactInfo {
  void visit(ContactInfoVisitor v) {
    v.handlePhoneNumber(this);
  }
}

class EmailAddress extends ContactInfo {
  void visit(ContactInfoVisitor v) {
    v.handleEmailAddress(this);
  }
}

This is an application of what the Gang-of-Four call the Visitor Pattern. Once again, you need access to your parameter classes (in the above example, all ContactInfo types). Furthermore, this approach requires that all code (the parameter types, the visitor interface and visitor implementations) be compiled at the same time. Post a comment if you are interested why this is, or if you seek techniques to relax this limitation.

4. Implement some dynamic dispatching solution yourself. Classification-based, type-based and defaulting-based are some popular implementation approaches. The previous post introduced a simple defaulting-based dispatching.

Post a comment or leave a trackback: Trackback URL.

Comments

  • Atul  On October 20, 2010 at 8:12 PM

    PLS chenge this site color and font.

Trackbacks

Leave a comment