I have two Visitor classes in my code Visitor1
and Visitor2
that execute functions on the classes ElementA
and ElementB
(both implement the interface Visitable
that defines accept(Visitor)
).
I do not want to support calling visit
from Visitor1
on ElementB
.
I thought of two different ways:
- Throw an exception when
Visitor1.visit(ElementB)
is called:
public class Visitor1 extends Visitor {
@Override
public void visit(ElementA element) {
//...
}
@Override
public void visit(ElementB element) {
throw new UnsupportedOperationException();
}
}
- Implement two different Visitor classes and one
accept
method per visitor:
public ElementA implements Visitable1, Visitable 2 {
public void accept(Visitor1 v) {
//...
}
public void accept(Visitor2 v) {
//...
}
}
public ElementB implements Visitable2 {
public void accept(Visitor2 v) {
//...
}
}
How should I implement this the best? The first solution contradicts the Interface Segregation Principle and the second solution contradicts the visitor pattern, as the visitors cannot be as easily extendable.
1 Answer 1
Here is a straightforward solution, expressing the case literally: implement ElementA.accept
in terms of the base class Visitor
, and ElementB.accept
in terms of Visitor2
:
public ElementA implements Visitable {
public void accept(Visitor v) {
//...
}
}
// no derivation from Visitable here, which would require
// to provide accept(Visitor v)
public ElementB {
public void accept(Visitor2 v) {
//...
}
}
This avoids the issue of two very similar ElementA.accept
methods, which leads to the extendability problem mentioned in your question.
However, this solution will not work if the intention is to have some generic code which calls visitable.accept(someVisitor)
without knowing beforehand if visitable
is of type ElementA
or ElementB
(both derived from Visitable
), and also not knowing if someVisitor
is of type Visitor1
or Visitor2
(both derived from Visitor
) . If that's the case, there is no way to prevent getting a combination of objects Visitor1
with ElementB
at runtime without loosing the genericity. Hence you need to decide how you want the calling code to behave in case someone passes the forbidden combination in there:
maybe you want the behaviour from solution 1, where the forbidden combination throws an exception (so the caller can react accordingly)
maybe you want nothing to happen - similar to solution 1, but with
Vistor1.visit(ElementB element)
implemented empty (so the caller does not have to care for)or maybe you don't need any generic caller. Then you can give up the genericity, make one function which calls
ElementA.accept
with arbitrary visitors, and a second one which callsElementB.accept
only with visitors of typeVisitor2
(using my suggestion from above).
So look at the requirements of the caller - ask yourself: how will you are going to use the visitors (why do you need them at all), and which degree of genericity are you trying to achieve?
visit(ElementB element)
as a no-op (similar to null object pattern)? Perhaps that makes sense, and it could make things simpler? 2/2