Skip to content

Tag: Class diagram

The Visitor Design Pattern with Sequence Diagrams

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 Elements 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 accept function… which is something classes don’t normally come with. This is wrong! there are many (and simpler) ways to do this, using decorators, adapters, etc. Read on…

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.

Enhanced by Zemanta