Sunday, July 6, 2014

What the Heck is Double Dispatch? And Does Dart Do It?


I have crazy gaps in my knowledge.

I have posted more than 1600 blog posts each of which answers a question that I could not answer when I started. One might think that I know a lot by know. Maybe I do, but there are still some crazy gaps in my knowledge. This is why I love programming.

Tonight, I would like to be able to answer what double dispatch means in object oriented methods. Having read the Gang of Four book, I think I might already be able to provide an acceptable answer. One of the more important lessons that I have learned 1600+ posts is that there is a world of difference between the ability to give an answer and the ability to teach an answer.

The answer that I would give at the outset of this little exercise to what is double dispatch would be a method that gets invoked based on two objects and one method. At the risk of further illustrating exactly how weird the gaps in my knowledge are, I would clarify that with a bit of Smalltalk terminology by saying that the method to be executed is resolved based on two receivers that receive a single message—the method being called.

Normally, a method is resolved by the type of the receiver and the message being sent to it. If I call my_list.join('\n'), I am sending the join message to an object of type List (the message is sent with a single argument that tells the receiver to join with newlines). In this case, I would expect to find the join() method defined in the List class.

I know from my studying of the Visitor Pattern that it relies on double dispatch in order to accomplish its purpose—an object that can operate on a data structure without changing the data structure. Let's see if I can work through my rote answer to internalize it...

I am still working with an inventory data structure. My work stuff inventory included a mobile, tablet, and laptop:
  var work_stuff = new InventoryCollection([
    new Mobile()
      ..apps = [
          app('2048', price: 10.0),
          app('Pixel Dungeon', price: 7.0),
          app('Monument Valley', price: 4.0)
        ],
    new Tablet()
      ..apps = [
          app('Angry Birds Tablet Platinum Edition', price: 1000.0)
        ],
    new Laptop()
  ]);
Normally if I were to call a method on work_stuff, I would look inside InventoryCollection to find the method definition. I might print the names of all of the top-level items in my collection with:
  print(work_stuff.names());
And I could find the full definition of what occurs by looking in the InventoryCollection class:
class InventoryCollection {
  List<Inventory> stuff = [];
  InventoryCollection(this.stuff);

  String names() =>
    stuff.map((i) => i.name).join('\n');

  void accept(visitor) {
    stuff.forEach((thing) { thing.accept(visitor); });
  }
}
But, if I invoke the accept() method in the same class, then I cannot find the ultimate method definition of the method called in InventoryCollection. I cannot even find it on one of the components of the collection like Laptop:
class Laptop extends Equipment {
  Laptop(): super('Laptop');
  double netPrice = 1000.00;
  double discountPrice() => netPrice * .9;
  void accept(visitor) { visitor.visitLaptop(this); }
}
It turns around and invokes the visitLaptop() method in the accept() method call:
  var cost = new PricingVisitor();
  work_stuff.accept(cost);
  print('Cost of work stuff: ${cost.totalPrice}.');
There are two receivers along the way, but it starts with a single dispatch with the object structure receiving the accept message. That message comes along with the cost visitor as a single argument. Once inside the accept() method of a node, then the visitor is sent a message—in this case visitLaptop.

So even though the initial method call looks like a plain-old single dispatch, it turns into a double dispatch. It does so by virtue of two different receivers being sent a message. The combination of the two receivers being sent method messages in response to one method call is the result. Taking that further, multiple dispatch would involve more objects receiving a message in response to a single initial method being sent.

I begin to internalize this concept. I still do not quite think that I have the ability to teach it, but hopefully this exercise combined with additional work on this pattern (and other code) will help me get there. Of course, how much I need to teach this in Design Patterns in Dart is an open question. The GoF discussed it, but not in sufficient depth for me to fully understand. Maybe that is enough for most of my readers, however. Food for future thought.


Day #114

No comments:

Post a Comment