I am working on a GUI application which generates a configuration file. I have a class hierarchy for the configuration model and I use an object tree of that hierarchy in several different contexts. Currently, I use the Visitor pattern to avoid polluting my model classes with context specific code.
interface IConfigurationElement {
void acceptVisitor(IConfigurationElementVisitor visitor);
}
In an earlier version I used chains of instanceof
conditions instead of the Visitor. Comparing the two approaches I see the following tradeofs.
Visitor
- It is easier and safer to add new
IConfigurationElement
. Just add a new declaration toIConfigurationElementVisitor
and the compiler generates errors for all visitor implementations. Withinstanceof
chains you have to remember all the places you have to extend with the new configuration element. Basicallyinstanceof
violates the DRY principle as it duplicates logic in several places. - The visitor pattern is more efficient than a chain of
instanceof
conditions
instanceof
- The great advantage of
instanceof
is its flexibility. For exampleinstanceof
allows me to define special solutions for different subsets ofIConfigurationElement
implementations which need to be handled similarilly in some cases. In contrast, Visitor forces me to implement a method for each implementation class every time.
Is there a common solution for this kind of problem? Can I adapt the Visitor somehow, so I can provide a common solution for some cases?
1
You could use visitor with instanceOf
interfaces:
interface Visitable {
void accept(Object visitor);
}
interface AVisitor {
void visitA(A a);
}
interface BVisitor {
void visitB(B b);
}
interface CVisitor {
void visitB(C c);
}
Visitables:
class C implements Visitable {
public void accept(Object visitor) {
if (visitor instanceof CVisitor) {
((BVisitor)vistor).visitC(this);
}
}
}
class B implements Visitable {
public void accept(Object visitor) {
if (visitor instanceof BVisitor) {
((BVisitor)vistor).visitB(this);
}
}
}
class A extends B implements Visitable {
public void accept(Object visitor) {
super.accept(visitor);
if (visitor instanceof AVisitor) {
((AVisitor)vistor).visitA(this);
}
}
}
Visitors:
class PrintBs implements BVisitor {
public void visitB(B b) {
system.out.println(b);
}
}
class PrintAs implements AVisitor {
public void visitA(A a) {
system.out.println(a);
}
}
class PrintCs implements CVisitor {
public void visitC(C c) {
system.out.println(c);
}
}
class PrintAsAndCs implements CVisitor, AVisitor{
public void visitA(A a) {
system.out.println(a);
}
public void visitC(C c) {
system.out.println(c);
}
}
each class knows only about its related interfaces, so adding new vistors nor visitables requires changing everything in that category (visitor/visitable) (for visitor, it does not require changing anything, for visitable it requires creating new visitor interface, but again, no changing of existing objects).
This way, there is no chain of instanceof tests and visitor for subset does not need to even know about types outside this subset.
Question is what to do with situation where A extends B (and B is Visitable too), in that case, you could just add super.accept(visitor) into accept (thus it would be short chain of instanceof-s, but onlu as long as hierarchy is deep, and it should not be too deep to matter, abd you don’t need to write it whole manually).
3
Well, yes, you could. Capture the commonalities of the many configuration elements by assigning roles to them. You may end up with instanceof, but not in a way that violates the DRY principle, but rather as a way around Java’s static typing.
class ConfigurationElementA implements IConfigurationElement, ICommittable {
}
class ConfigurationElementB implements IConfigurationElement, IVerifiable {
}
class Visitor {
void accept(IConfigurationElement element) {
if (element instanceof ICommittable) {
// ...
}
// Note: not a chain of instanceofs.
if (element instanceof IVerifiable) {
// ...
}
}
}
In other words, you let the visitor accept configuration elements generically, and act on groups of implementations via roles. Note that you can go as specific as you need by modeling your configuration elements accordingly.
You may recognize here Martin Fowler’s RoleInterface pattern.
3
I can think of some potential solutions:
-
create a private method in the
Visitor
implementation, and have multiplevisit
methods in theVisitor
implementation call that private method. -
if the above is repeated in many places, you can consider creating an abstract class that implements
Visitor
, and redirects a subset ofvisit
implementations to a commonprotected abstract
method. -
create multiple
Visitor
interfaces:interface AOrB extends IConfigurationElementVisitor { <Result> Result accept(AOrBVisitor<Result> visitor); interface AOrBVisitor<Result> { Result visit(A a); Result visit(B b); } } interface ThisCouldBeOfTypeB extends IConfigurationElementVisitor { <Result> Result accept(ThisCouldBeOfTypeBVisitor<Result> visitor); interface ThisCouldBeOfTypeBVisitor<Result> { Result visit(B b); Result visit(ThisCouldBeOfTypeB visitable); } } class A implements AOrB, ThisCouldBeOfTypeB {...} class B implements AOrB, ThisCouldBeOfTypeB {...} class C implements ThisCouldBeOfTypeB {...}
I think 3 is what you are looking for. it is 100% static polymorphism, and generates compiler warnings if a type isn’t handled. But the only disadvantage I can think of 3 is that maintaining the Visitable*
implementations could become complex if there are a lot of different Visitable*
interfaces.