John Vlissides, one of the GoF, wrote an excellent chapter on the subject in his Patterns Hatching book. He discusses the very concern that extending the hierarchy is incompatible with maintaining the visitor intact. His solution is a hybrid between a visitor and an enum
-based (or a type-based) approach, where a visitor is provided with a visitOther
method called by all classes outside the "base" hierarchy that the visitor understands out of the box. This method provides you an escape way to treat the classes added to the hierarchy after the visitor has been finalized.
abstract class Visitable {
void accept(Visitor v);
}
class VisitableSubclassA extends Visitable {
void accept(Visitor v) {
v.visitA(this);
}
}
class VisitableSubclassB extends Visitable {
void accept(Visitor v) {
v.visitB(this);
}
}
interface Visitor {
// The "boilerplate" visitor
void visitB(VisitableSubclassA a);
void visitB(VisitableSubclassB b);
// The "escape clause" for all other types
void visitOther(Visitable other);
}
When you add this modification, your visitor is no longer in violation of the Open-Close Principle, because it is open to extension without the need to modify its source code.
I tried this hybrid method on several projects, and it worked reasonably fine. My main class hierarchy is defined in a separately compiled library which does not need to change. When I add new implementations of Visitable
, I modify my Visitor
implementations to expect these new classes in their visitOther
methods. Since both the visitors and the extending classes are located in the same library, this approach works very well.
P.S. There is another article called Visitor Revisited discussing precisely that question. The author concludes that one can go back to an enum
-based double dispatch, because the original Visitor Pattern does not present a significant improvement over the enum
-based dispatch. I disagree with the author, because in cases when the bulk of your inheritance hierarchy is solid, and the users are expected to provide a few implementations here and there, a hybrid approach provides significant benefits in readability; there is no point in throwing out everything because of a couple of classes that we can fit into the hierarchy with relative ease.