Listen to this post
|
The Gang of Four book mentions that the Visitor design pattern is a way of implementing double dispatch, but what is double dispatch? I’ve seen this question leave a room full of professional software engineers stumped; engineers from different companies, who specialise in different languages. The commonality between them all was that not one of those languages supports double dispatch. Many of the articles I’ve looked at since used flowery and confusing language, and often lacked useful code snippets. So please allow me to explain, using code snippets and straightforward language.
Java Example
Java only implements single dispatch, which means it uses the runtime type of the method’s class to determine which method to route to. However, it uses the compile time type of the methods parameters. Flowery talk? Let’s see the example, but before we start, this is just an example I made up, the super class is not abstract, because I could not illustrate the point so well if it was. Don’t hate me… Anyway, onward!
SpaceSimulatorClient.java
public class SpaceSimulatorClient { public static void main(String[] args){ ImperialShip unscannedShip = new ImperialShip(); ImperialShip enemyUnscannedShip = new ImperialShip(); ImperialShip scannedShip = new DauntlessCruiser(); ImperialShip enemyScannedShip = new DauntlessCruiser(); DauntlessCruiser enemyShipInVisualRange = new DauntlessCruiser(); unscannedShip.fireUpon(enemyUnscannedShip); //1 unscannedShip.fireUpon(enemyScannedShip); //2 unscannedShip.fireUpon(enemyShipInVisualRange);//3 scannedShip.fireUpon(enemyUnscannedShip); //4 scannedShip.fireUpon(enemyScannedShip); //5 scannedShip.fireUpon(enemyShipInVisualRange); //6 } }
DauntlessCruiser.java
public class DauntlessCruiser extends ImperialShip { public void fireUpon(ImperialShip enemy){ System.out.println("I know there is an enemy there, but I see no specifics!"); } public void fireUpon(DauntlessCruiser enemy){ System.out.println("Firing all 4 starboard batteries at the enemy. We expect to get at least 1 hit through their shields!"); } }
ImperialShip.java
public class ImperialShip { public void fireUpon(ImperialShip enemy){ System.out.println("Apologies captain, but I do not know the specifics of this vessel, nor the enemy!"); } public void fireUpon(DauntlessCruiser enemy){ System.out.println("I see the Dauntless, but I do not know the specifics of our vessel!"); } }
Which output:
-
Apologies captain, but I do not know the specifics of this vessel, nor the enemy!
-
Apologies captain, but I do not know the specifics of this vessel, nor the enemy!
-
I see the Dauntless, but I do not know the specifics of our vessel!
-
I know there is an enemy there, but I see no specifics!
-
I know there is an enemy there, but I see no specifics!
-
Firing all 4 starboard batteries at the enemy. We expect to get at least 1 hit through their shields!
Comparing the output to what we expect to be printed, you may be confused by lines 2 and 5. However, the problem is that these are using the compile time types of the parameters involved, as Java is supposed to. Let me doodle on my code to explain:
Basically, Java is going to call the method of the runtime object with the compile time parameters. If the compile time parameters do not match their runtime counterparts, you will observe this slightly strange Single Dispatch behaviour.
What is Going on Here?
Where other languages use method name mangling to support method overloading, Java uses method descriptors, but they both basically serve the same purpose; they allow the interpreter to find the correct version of the method quickly. You can see how simultaneously searching for the correct type and number of parameters, while ascertaining their runtime type, for every method call would be costly in terms of processor time. Just think about how that could explode when you have more parameters. So Java chooses performance over the lesser used double dispatch feature. It will become apparent that I don’t even need it in the above example, but I’ll come to that.
C# Example
All of this begs the question, what would double dispatch look like if it just worked, so I did some googling and took a look at C#, Java’s nemesis! Now, I have never written C# before, so please go easy on my code. I choose it because it can, with a light touch, be made to support double dispatch by leveraging the ‘dynamic’ key word.
SpaceSimulatorClient.cs
class SpaceSimulatorClient { public static void Main(string[] args) { OrkShip unscannedShip = new OrkShip(); OrkShip enemyUnscannedShip = new OrkShip(); OrkShip scannedShip = new OrkSavageGunship(); OrkShip enemyScannedShip = new OrkSavageGunship(); OrkSavageGunship enemyShipInVisualRange = new OrkSavageGunship(); unscannedShip.fireUpon((dynamic) enemyUnscannedShip); unscannedShip.fireUpon((dynamic) enemyScannedShip); unscannedShip.fireUpon((dynamic) enemyShipInVisualRange); ((dynamic) scannedShip).fireUpon((dynamic) enemyUnscannedShip); ((dynamic) scannedShip).fireUpon((dynamic) enemyScannedShip); ((dynamic) scannedShip).fireUpon((dynamic) enemyShipInVisualRange); } }
OrkSavageGunship.cs
class OrkSavageGunship: OrkShip { public void fireUpon(OrkShip enemy) { Console.WriteLine("Der is enemy out der, but I don't see it!"); } public void fireUpon(OrkSavageGunship enemy) { Console.WriteLine("Firing all 4 Heavy gunz. We should hit 'em good fer at least 2 booms!"); } }
OrkShip.cs
class OrkShip { public void fireUpon(OrkShip enemy) { Console.WriteLine("Who is they? Who is us?! I is so confusted boss!"); } public void fireUpon(OrkSavageGunship enemy) { Console.WriteLine("I sees de gunship, where is our gunz?!"); } }
This outputs exactly what we would expect. You can see how the dynamic casting here is doing the heavy lifting. By the time it comes to actually finding the correct method to call, the runtime type is being used. You can also see how dynamic casting is required to allow single dispatch. So a point for Java then? I’m kidding! I don’t know enough about C# to form a full opinion on which is the superior language.
What Did Visitor Do That Was So Special?
The simple answer is that in the visitor implementation, as described in the book, there is an abstract class where we have ImperialShip (or OrkShip in the C# example) that has a method for receiving each of the expected types of visitor (read firing ship) visiting them (read be firing upon by them). In our Java example it would look something like this:
public abstract class ImperialShip { // One of these for every concrete sub-class of ImperialShip public abstract void fireUpon(DauntlessCruiser enemy); public abstract void firedUponBy(ImperialShip enemy); }
And each implementation would need to overload these methods as follows:
public class DauntlessCruiser extends ImperialShip { // One of these for every concrete sub-class of ImperialShip public void fireUpon(DauntlessCruiser enemy){ System.out.println("Firing all 4 starboard batteries at the enemy. We expect to get at least 1 hit through their shields!"); } public void firedUponBy(ImperialShip enemy){ enemy.fireUpon(this); } }
You can see how this switches around the one parameter with the object that was called. It’s basically using single dispatch twice! If you’re looking at it and thinking that it seems horribly convoluted for this use case, you are one hundred percent correct. There’s a voice in my head yelling “I only need the distance to the other ship!”. However, as seen in the Visitor Design pattern, there are times when this is the best way to do things.
So What is Double Dispatch Then?
Hopefully, we have all learned that double dispatch is when methods are dispatched based on the run time (or dynamic) type of two things; the object being called and its parameters.
Java only supports single dispatch, AKA only pays attention to the runtime type of one thing, the object that the method was called upon. For the arguments (AKA second thing), it just uses the compile time types. Why didn’t I just say that at the beginning? I honestly don’t know, but at least if you’re in a room full of nerds failing to understand double dispatch in the future, you can tell them all about spaceships. I’m sure it will wow them into silence!
Until next time, happy coding!