I am having a problem with the Visitor pattern and generics. I have some abstract class whose children are to be visited. Look at this code:
public abstract class Element extends SomeSuperClass {
public void accept(Visitor<? extends Element> v) {
v.visit(this);
}
}
public interface Visitor<T extends SomeSuperClass> {
void visit(T element);
}
So the idea is: I have some class hierarchy (e.g. Element
is a subclass of SomeSuperClass
).
I have got some generic Visitor
interface to visit this hierarchy. Now in the middle of this hierarchy is the Element
class, which is abstract and has it's own subclasses.
Now I want Element
to accept all visitors of its subclasses, which is why I put this line:
public void accept(Visitor<? extends Element> v)
But now I am receiving error:
The method visit (
capture#1-of ? extends Element
) in the typeVisitor<capture#1-of ? extends Element>
is not applicable for the arguments (Element
).
I understand that ? extends Element
is not Element
. My question is: Can I express my idea in a different way? Or I have just missed the idea of generics in this case?
Note that T
in <T extends SomeSuperClass>
can be a type completely unrelated to Element
and the compiler must ensure for the general case that visit(T t)
will work for every possible T
.
The code you have calls Visitor.visit(Element e)
, but the visitor in question could be Visitor<SubElement>
. That wouldn't make sense.
I think that the requirement "Element
must accept all visitors of its subclasses" doesn't make sense: the visitor must at least be able to visit Element
and all its subclasses. That would be a Visitor<Element>
.
The construct accept(Visitor<? extends Element> v)
means that v
can be any such Visitor<T>
that T extends Element
. It does not mean that the visitor itself will be of the type Visitor<? extends Element>
. In fact, no such thing even exists in Java. Every Visitor will have a specific type parameter associated with it, not a wildcard.
That cannot work - the visitors of ? extends Element
may need to be able to access data (attributes / methods, ...) that Element
doesn't have or know about.
You can't get a visitor that's supposed to visit objects that extends Element
to necessarily be able to visit something that's a straight Element
or even another, completely separate, subclass of Element
.
I don't think that what you're trying to do makes much sense. Making Visitor
generic is useless: the accept()
method must take a specific visitor interface as argument, so that subclasses of Element
may be able to call specific overloads of visit()
.
interface Visitor {
void visit(Element e);
void visit(SubElement e);
}
class Element {
public void accept(Visitor v) {
v.visit(this);
}
}
class SubElement {
public void accept(Visitor v) {
v.visit(this);
}
}
class ElementVisitor implements Visitor {
public void visit(Element e) {}
public void visit(SubElement e) {}
}
Note that the Visitor
interface must be aware of all the classes in the Element
hyerarchy that need a custom visit()
implementation.
The most general way to write it would be:
public void accept(Visitor<? super Element> v) {
v.visit(this);
}
That way, even a Visitor<Object>
would work (why shouldn't it).
Remember PECS (producer extends
, consumer super
). The visitor is a consumer, so it should be super
.
来源:https://stackoverflow.com/questions/12109698/generics-and-the-visitor-pattern