Chad
Chad

Reputation: 2071

How do I make Visitor Pattern more extensible by making subtype elements require specific subtype visitors via generics?

I have the following setup defined as follows:

public interface Element {
    public <R> R accept(Visitor<R> visitor);
}

public interface Visitor<R> {

}

class SpecificElement implements Element {
    @Override                                    // ERROR: didn't override accept in parent
    public <R> R accept(SpecificVisitor visitor) {
        return visitor.visitSpecificElement(this);
    }
}

class SpecificVisitor implements Visitor<Boolean> {
    public Boolean visitSpecificElement(SpecificElement element) {
        return true;
    }
}

How can I change the setup above such that I can require subtype SpecificElement to only accept SpecificVisitor and able to override parent class' accept method.

What I want to happen is to mandate that the implementing class SpecificElement will only accept visitors of type SpecificVisitor and nothing else, but still keep the signature of Element's type interface at Visitor.

UPDATE 1: I added the type parameter V extends Visitor<?> in Element, then extended it with SpecificElement extends Element<SpecificVisitor<?> and was able to get what I want (stricter parameter types in method accept): <R> R accept(SpecificVisitor<?> visitor). However my problem now is that I want to determine the return type of the method accept based on the type parameter of the argument visitor (in this case ?). Changing ? to R causes compile error.

public interface Element<V extends Visitor<?> {
    public <R> R accept(Visitor<R> visitor);
}

public interface Visitor<R> {

}

class SpecificElement implements Element<SpecificVisitor<?>> {
    @Override                                    
    public <R> R accept(SpecificVisitor<R> visitor) { // error in type parameter R in SpecificVisitor
        return visitor.visitSpecificElement(this);
    }
}

class SpecificVisitor implements Visitor<Boolean> {
    public Boolean visitSpecificElement(SpecificElement element) {
        return true;
    }
}

UPDATE 2: I would like to write a family of Visitors for a family of Elements without giving up type safety. Notice the use of same Visitor subtype for an Element subtree. For Example:

--- Element -> Visitor<R>
       |
       |---- SpecificElement -> SpecificVisitor<R>
       |            |
       |            |---------SpecificElementA -> SpecificVisitor<R>
       |            |
       |            |---------SpecificElementB -> SpecificVisitor<R>
       |
       |---- FancyElement -> FancyVisitor<R>
                    |
                    |---------FancyElementA -> FancyVisitor<R>
                    |
                    |---------FancyElementB -> FancyVisitor<R>

Upvotes: 0

Views: 1100

Answers (2)

Dan Getz
Dan Getz

Reputation: 9152

This can be accomplished, as long as you know your subtypes you want to use in advance. (You're using the visitor pattern, so you need to know all the element types in advance, anyway.) The important thing is to build your Visitor interface from the bottom up, not the top down:

public interface ElementVisitor<R> extends SpecificVisitor<R>, FancyVisitor<R> {
}

public interface Element {
    public <R> R accept(ElementVisitor<R> visitor);
}

Now Element subtypes will need a little boilerplate, so that they can implement the Element interface:

public abstract class FancyElement implements Element {
    public abstract <R> R accept(FancyVisitor<R> visitor);

    public <R> R accept(ElementVisitor<R> visitor) {
        return accept((FancyVisitor<R>) visitor);
    }
}

And then we can define the specific subtype visitors and elements:

public class FancyElement1 extends FancyElement {
    public <R> R accept(FancyVisitor<R> visitor) {
        return visitor.visit(this);
    }
}

public interface FancyVisitor<R> {
    public R visit(FancyElement1 e);
    // ...
}

… and repeat for SpecificVisitor.

Now you can define either a FancyVisitor that can only visit FancyElements, a SpecificVisitor that can only visit SpecificElements, or an ElementVisitor that can visit all Elements.

Upvotes: 1

irakli2692
irakli2692

Reputation: 137

maybe this version will be usefull.

make Element interface generic

interface Element<V> {
    <R> R accept(V visitor);
}

and SpecificElement should be so:

public class SpecificElement implements Element<SpecificVisitor> {
    public <R> R accept(SpecificVisitor visitor) {
        return visitor.visit();
    }
}

note that Element_s generic type V is not strict type, there can be anything. implementor of Element interface, can write any class in it, even if it doesn't implement Visitor interface. this is the disadvantage of this code.

Upvotes: 0

Related Questions