1

I'm looking for a clean design to emulate Visitor functionality without the many drawbacks it has. In Java, the traditional implementations (as the described in GoF) resort to double dispatch to get rid of if-elses. To solve this, I've seen some implementations that use reflection to avoid modifications on the "Visitable" classes, but these rely on hardcoded strings when looking for method names. Although quite useful, I still think that they are not clean design.

Is it possible to emulate the same idea using data structures and/or good OO-design? It doesn't have to be a pattern, just I'm looking for examples where a similar problem is solved (e.g.: using a Map<Class<T>,SomeFunctionObject>).


UPDATE Something like this:

    public abstract class BaseVisitor<T> {

        private final TypesafeHeterogeneusMap map;

        protected BaseVisitor(){
            map = inflateFunctions();
        }   

        public <E extends T> void  process(E element){
            if(element == null){
                throw new NullPointerException();
            }
            boolean processed = false;

            @SuppressWarnings("unchecked")
            Class<? super T> sc = (Class<? super T>) element.getClass();

            while(true){            
                if(sc != null){
                    FunctionObject<? super T> fo2 = map.get(sc);
                    if(fo2 != null){
                        fo2.process(element);
                        processed = true;
                        break;
                    }
                    sc = sc.getSuperclass();
                } else {
                    break;
                }
            }

            if(!processed) System.out.println("Unknown type: " + element.getClass().getName());     
        }

        abstract TypesafeHeterogeneusMap inflateFunctions();
    }

Actually is a mix of Template pattern and Command pattern, I think. Feel free to post your suggestions on how to enhance it.

Mister Smith
  • 27,417
  • 21
  • 110
  • 193
  • It's hard to give a solution without knowing what the problem is. What's the problem with the visitor pattern that you would like to solve? Could you give an example? – JB Nizet Jan 12 '12 at 14:22
  • 1
    @JB Nizet The problem with Visitor come from the fact that with the traditional approach (double-dispatch), you can't add a new Visitable without modifying both Visitor interface and implementations. – Mister Smith Jan 12 '12 at 14:26

3 Answers3

3

You could just make all your Visitor implementations extend a base class, which provides a default implementation for every type of Visitable:

public interface AnimalVisitor {
    void visitHorse(Horse horse);
    void visitDog(Dog dog);
}

public class BaseAnimalVisitor implements AnimalVisitor {
    public void visitHorse(Horse horse) {
        // do nothing by default
    }
    public void visitDog(Dog dog) {
        // do nothing by default
    }
}

Then, when a new class Cat is introduced, you add the visitCat(Cat cat) method to the interface and the base class, and all the visitors are left unchanged and still compile. If they don't want to ignore cats, then you override the visitCat method.

JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • This is still Visitor. You save from modifying implementations but you are still modifying interfaces and base visitor class. What I was looking for is some example where the dynamic dispatch has been solved with a data structure (hashtable, map) along with some other patterns (maybe Command?), while keeping the new Visitor functionality localized. – Mister Smith Jan 12 '12 at 14:43
  • New functionality can't happen without introducing some additional code. I really don't see where you want to go. You could have a single visitAnimal method, and do a chain of if calls to determine the type, or a lookup by class in a map, but it would completely defeat the point of the visitor pattern. – JB Nizet Jan 12 '12 at 14:54
  • I'm thinking on a "Visitor" class as a class holding a `Map`, which defines a set of `FunctionObjects` as factory methods, putting them in the Map in the constructor. Thus if you add a new Visitable class to the hierarchy, the "Visitor" class doesn't have to be modified unless it has to process the new type. Perhaps what I'm saying is not a Visitor at all XD. – Mister Smith Jan 12 '12 at 16:11
  • I don't really see what you gain by doing this over my solution. You'll lose type safety, though, since each FunctionObject will have to cast its input to the desired subclass. – JB Nizet Jan 12 '12 at 16:26
  • That point is where I'm lost. How could I achieve typesafety? I made the `FunctionObject` interface generic, but I just can't fit it with the map thing. I need a `Map,FunctionObject>` where E is a subtype of `Visitable`. – Mister Smith Jan 12 '12 at 16:56
  • I really don't see how it could be possible. If all the visitable classes call the same visitor method, this method must either use the base class as argument, or be generic. If it isn't generic, you lose type safety. If it's generic, then you have to get a FunctionObject from somewhere. But if the FunctionObject instances all stored in a single map, you'll lose type safety at some point. I don't see what you gain by refusing to add a typed method to the interface. It's type-safe, and simple to implement. There's a good reason the pattern is implemented this way. – JB Nizet Jan 12 '12 at 17:02
2

Although it's not the answer you're looking for: Consider using a higher-level, less verbose language than Java. You will find that things like the Visitor pattern start to seem irrelevant. Of course, if you want to define logic for traversing a data structure in one place, and define what to do with the elements of the data structure (based on their types) somewhere else, and make it possible to mix-and-match traversal/processing strategies, you can do that. But you can do it using just a small amount of straightforward code, nothing that you would think of calling a "pattern".

I came from a C/Java programming background and started learning various dynamic languages a few years ago. It was mind-blowing to realize how much you can do in a few lines of code.

For example, if I was to emulate the Visitor pattern in Ruby:

module Enumerable
  def accept_visitor(visitor)
    each do |elem|
      method = "visit#{elem.class}".to_sym
      elem.send(method,elem) if elem.respond_to? method
    end
  end
end

To explain: in Ruby, an Enumerable represents anything which can be iterated over. In those 8 lines of code, I have made every kind of object which can be iterated over accept Visitors. Whether I plan to have 5, 10, or 100 different classes accept Visitors, those 8 lines are all that are needed.

Here's a sample Visitor:

class CatCounter
  attr_reader :count
  def initialize; @count  = 0; end
  def visitCat;   @count += 1; end
end

Note that the Visitor doesn't have to define methods for all the different types of Visitables. Each Visitor just has to define methods for the types of Visitables it is interested in; it can ignore the rest. (Which means you don't have to modify a bunch of existing code if you add a new type of Visitable.) And any Visitor can interoperate with any object which accepts Visitors.

Just in those few lines of code, all the problems you mentioned with the Visitor pattern have been overcome.

Don't get me wrong; Java is a great language for some things. But you need to choose the right tool for the job. The fact that you are fighting so much to overcome the limitations of your tool might indicate that in this case, a different tool is called for.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • Thanks, this is really nice. Although I can't understand most of the code, but seems really concise and effective. Unfortunately, I don't have the possibility of choosing another platform/language. Agree in that Java is not suitable for some things, but a good programmer shouldn't, in my opinion, refuse to make good design just because the language doesn't provide the right mechanisms out of the box. Look, C and C++ are also old and "dirty", but even with these you can write elegant code. – Mister Smith Jan 24 '12 at 08:40
  • Understood. If you do have to use Java, though, and using the Visitor pattern makes your code very verbose (which also makes it hard to refactor), then is it really "good design"? Using Gang of Four patterns doesn't equal good design, or elegance. In the majority of cases where I see people using the Visitor pattern, it isn't called for. Before you use a Visitor, consider whether straightforward code would work just as well. – Alex D Jan 24 '12 at 09:49
  • In my opinion the original Visitor as described in GoF is not good design. That's the purpose of my question. – Mister Smith Jan 24 '12 at 09:59
  • @MisterSmith, check out my new answer (especially the edit I just made)! – Alex D Jan 24 '12 at 10:25
0

@MisterSmith, since you have to use Java, and presumably you do have good reasons for using Visitor, I am going to propose another possible solution.

Let's separate our minds from the way Visitor is usually implemented and go back to the reason why people use Visitors in the first place. Although I mentioned it already in my other answer, the point of Visitor is to make it possible to mix-and-match traversal and processing logic.

"Traversal logic" could mean logic for traversing different types of data structures, or traversing the same data structure in a different order. Or it could even include traversal strategies which apply certain filters to the elements returned, etc.

Implicit in Visitor is the idea that the processing we apply to each element is going to depend on its class. If what we do to each element doesn't depend on its class, there is no reason to use Visitor. Unless we want to do a "switch" on element class, we need to use virtual method calls to do this (which is why the usual Java implementation uses double dispatch).

I propose that we can split the Visitor pattern into 3 rather than 2 parts:

  1. An Iterator object which implements a certain traversal

  2. An object which implements the strategy of "deciding what to do with an element based on its class" (the part which normally requires double dispatch). Using reflection, we can make a general-purpose class which does this. A simple implementation would use a Map, or you could make something which generates bytecode dynamically (I forget the platform method in Java which lets you load raw bytecodes as a new Class, but there is one). OR! OR, you could use a dynamic, JVM-hosted language like JRuby or Clojure to write #2, compile to bytecode, and use the resulting .class file. (This file would probably use the invokedynamic bytecode, which as far as I know, is not accessible from Java -- the Java compiler never emits it. If this has changed, please edit this post.)

  3. The Visitors themselves. In this implementation, Visitors won't have to subclass from a common superclass, nor will they have to implement methods for elements they're not interested in.

Keeping the traversal in a general-purpose Iterator allows you to do other things with it (not just accepting Visitors).

There are a couple ways the 3 pieces could be tied together; I'm thinking #2 will wrap #3 (taking it as a constructor argument). #2 will provide a public method which takes an Iterator as an argument, and applies the Visitor to it.

The interesting part is #2. I may edit this post later to add a sample implementation; right now I have some other things to do. If someone else comes up with an implementation, please add it here.

Alex D
  • 29,755
  • 7
  • 80
  • 126
  • For me the point of visitor is adding new functionality to a hierarchy of visitable classes without changing them. Achieving this without resorting to the GoF implementation is straightforward, but the point of my question is how to do so without using if-else everywhere, or hardcoding class names (or using instanceof checks). But agree with you, and I tend much more to use Strategy or Template instead of Visitor. I was asking for a solution in plain standard Java (despite I didn't mention this in my question, because I don't want people refraining to post good answers as yours) – Mister Smith Jan 24 '12 at 12:11
  • About the traversal thing, I hate when I find a composite class in the visitable hierarchy actually performing traversal over its child nodes. This is awful design, and I see the Visitor implementation as the one responsible for this task. Again, one of the many drawbacks this pattern has, since it breaks the "One Class- One Responsibility" principle (among others). – Mister Smith Jan 24 '12 at 12:17
  • If you are creating any kind of data structure (including a Composite), "iterating over all the elements" is one of the basic operations you want to provide. If you don't like putting an iteration method in the Composite itself, you can put it in a separate Iterator class (which is what I am suggesting in this solution). – Alex D Jan 24 '12 at 13:12
  • About "adding functionality to a hierarchy of visitable classes without changing them", I think this solution fits the bill. If you are interested in an implementation which is interoperable with Java (via `.class` files), I think I can come with something. This could also be done in pure Java, using reflection. – Alex D Jan 24 '12 at 14:25