Last updated on 2014-09-07
The visitor design pattern a pattern to support separation of concerns in programming languages that support method overloading (OO languages, but also functional languages can work). The pattern is hard to understand first, but it is very useful once understood.
A Visitor
is a class that has a method visit
which is overriden for every class that the Visitor
can visit. Classes that can be visited have a method called accept(Visitor)
that when called, simply forwards the call to the Visitor
instance that was passed to it. So the actual logic of accept
is implemented in the Visitor
and not in the visited class. This is the standard class diagram for the visitor DP:
Notice that the Element
class is aware of the Visitor
interface, but doesn’t have to know which visitors exist. On the other hand, the Visitor
must know all of the Element
s it can visit. The reason for this dependency is that the visitor design pattern works because the Visitor.visit(Element)
function is overloaded, and therefore when the Element
calls Visitor.visit(this)
, the actual function called depends on the runtime type of the Element
. Because this design pattern is very hard to understand statically, a sequence diagram can come in handy, and here it is:
The implementation of Element.accept(Visitor)
simply forwards the call to the visitor with this
as its argument, in turn calling the matching overloaded visit
function.
If you have a good, real world example of a good use of this pattern, leave me a comment. While this pattern is very interesting, the only reason it should be used is when you want to add new functions to existing classes without changing them. But at the same time, these classes should already have an This is wrong! there are many (and simpler) ways to do this, using decorators, adapters, etc. Read on…accept
function… which is something classes don’t normally come with.
I was wrong on when this pattern should be used. After reading a lot of material available in the internet, I finally understood that this pattern should be used when you want to implement double-dispatch, meaning that both the accept
and the visit
methods are selected at runtime and not at compile time (Thanks to George Mauer for his answer to this question, which started my search). The best way to understand this is with an example. Suppose you have the following code:
public class Test { public static class A {} public static class B extends A {} public static class Caller1 { public void call(A a) { System.out.println("Calling with A"); } public void call(B b) { System.out.println("Calling with B"); } } public static void main(String args[]) { A a = new A(); B b = new B(); A bAsA = b; Caller1 c = new Caller1(); System.out.println("Static dispatch."); c.call(a); c.call(b); c.call(bAsA); } }
The result of executing this program may surprise the uninitiated:
Static dispatch. Calling with A Calling with B Calling with A
Wait… wasn’t the last call done with an instance of B
? It was, but in Java (C++ and other OO languages), the last function call is resolved at compile time based on the type of the argument variable (and not the type of instance that is contained in that argument). This is where double dispatch comes along to fix the problem:
public class Testing { public static class A { public void accept(Caller1 caller) { caller.call(this); } } public static class B extends A { public void accept(Caller1 caller) { caller.call(this); } } public static class Caller1 { public void call(A a) { System.out.println("Calling with A"); } public void call(B b) { System.out.println("Calling with B"); } } public static void main(String args[]) { A a = new A(); B b = new B(); A bAsA = b; Caller1 c = new Caller1(); System.out.println("Static dispatch."); c.call(a); c.call(b); c.call(bAsA); System.out.println("Double dispatch."); a.accept(c); b.accept(c); bAsA.accept(c); } }
And you can see the difference in the output:
Static dispatch. Calling with A Calling with B Calling with A Double dispatch. Calling with A Calling with B Calling with B
Much better. This works because the accept
function that is called is the one owned by the specific instance, regardless of the type of the variable holding this instance. And from there, the runtime finds the correct call
function.
Notice that we have to write an accept
method in each of the classes we want to use, otherwise the results are even stranger. For example, I deleted the accept
function in class B
and these is the result:
Double dispatch. Calling with A Calling with A Calling with A
This has been for me a very good lesson on OOP and polymorphism. And also on how it is always important to understand how my tools work. Oh, and BTW, some call double-dispatch a code-smell. I agree with them.
Be First to Comment