Better Explanation of @OverrideOnly

I think I may not have done such a good job explaining @OverrideOnly and the problems with visitors with the template method pattern a few days ago.

Here’s where the situation comes up: Let’s say you have a binary tree, realized using the union and composite patterns. It can either be a leaf, have one child or two children.

interface IBinTree {
public R execute(IBinTreeVisitor visitor, P param);
}

interface IBinTree {
public R leafCase(Leaf host, P param);
public R oneCase(OneNode host, P param);
public R twoCase(TwoNode host, P param);
}

class Leaf implements IBinTree {
public R execute(IBinTreeVisitor visitor, P param) {
return visitor.leafCase(this, param);
}
}

class OneNode implements IBinTree {
public R execute(IBinTreeVisitor visitor, P param) {
return visitor.oneCase(this, param);
}
}

class TwoNode implements IBinTree {
public R execute(IBinTreeVisitor visitor, P param) {
return visitor.twoCase(this, param);
}
}

Let’s say we’re now checking a structure like this, and we know we expect a OneNode, and in all other cases it should throw an exception. This comes up very often, for example when working with Java class files: I know that the referenced item should be a class, but what if it’s not?

Since this is so common, we define an abstract class using the template method pattern:

abstract class ADefaultBinTreeVisitor implements IBinTreeVisitor {
public R leafCase(Leaf host, P param) { return defaultCase(host, param); }
public R oneCase(OneNode host, P param) { return defaultCase(host, param); }
public R twoCase(TwoNode host, P param) { return defaultCase(host, param); }
public abstract R defaultCase(IBinTree host, P param);
}

This class delegates all calls to a default method that has to be overridden. To do the checking for OneNode, we would write something like this:

myTree.execute(new ADefaultBinTreeVisitor() {
public Void defaultCase(IBinTree host, Void param) {
throw new RuntimeException("OneNode expected!");
}
public Void oneCase(OneNode host, Void param) {
// do whatever we have to do
return Void.TYPE;
}
}, Void.TYPE);

Let’s say we’re lazy and we copy and paste this class so that we check for TwoNode. It’s very easy to incorrectly change the signature of the oneCase method above:

myTree.execute(new ADefaultBinTreeVisitor() {
public Void defaultCase(IBinTree host, Void param) {
throw new RuntimeException("TwoNode expected!");
}
public Void twoCase(OneNode host, Void param) {
// do whatever we have to do
return Void.TYPE;
}
}, Void.TYPE);

In this anonymous inner class, we have not overridden the twoCase method as intended; the twoCase method still calls defaultCase and throws an exception. Instead, we have added a new method twoCase that takes a OneNode as first parameter. This method is useless and will never called, but the compiler does not complain.

If we told the Java compiler or some checking tool that classes extending ADefaultBinTreeVisitor have to override or implement one of the methods already defined, and that they may not introduce new methods like we accidentally did above, this problem could be avoided:

@OverrideOnly
abstract class ADefaultBinTreeVisitor implements IBinTreeVisitor {
public R leafCase(Leaf host, P param) { return defaultCase(host, param); }
public R oneCase(OneNode host, P param) { return defaultCase(host, param); }
public R twoCase(TwoNode host, P param) { return defaultCase(host, param); }
public abstract R defaultCase(IBinTree host, P param);
}

myTree.execute(new ADefaultBinTreeVisitor() {
public Void defaultCase(IBinTree host, Void param) {
throw new RuntimeException("TwoNode expected!");
}
// ERROR: this method does not occur in the superclass
public Void twoCase(OneNode host, Void param) {
// do whatever we have to do
return Void.TYPE;
}
}, Void.TYPE);

This is the one error that I run into the most often with visitors and the template method pattern, and this would be really easy to check for a compiler.

Share

About Mathias

Software development engineer. Principal developer of DrJava. Recent Ph.D. graduate from the Department of Computer Science at Rice University.
This entry was posted in Research. Bookmark the permalink.

Leave a Reply