views:

778

answers:

5

What is the difference between applying the visitor design pattern to your code , and code like the following :

interface Dointerface {
    public void perform(Object o);

}

public class T {

    private Dointerface d;
    private String s;

    public String getS() {
            return s;
    }

    public T(String s) {
            this.s = s;
    }

    public void setInterface(Dointerface d) {
            this.d = d;
    }

    public void perform() {
            d.perform(this);
    }

    public static void main(String[] args) {
            T t = new T("Geonline");
            t.setInterface(new Dointerface() {
                    public void perform(Object o) {
                            T a = (T)o;
                            System.out.println(a.getS());
                    }
            });
            t.perform();
    }

}

I assume that by using interfaces , we're not really separating the algorithm.

A: 

The only thing that I see that is readily obvious is that by storing the interface, you make it so you have to do two operations rather than one to invoke it. I suppose that this could make sense if you are repeatedly going to perform the same action once the interface is set, but I think you could stick with the standard Visitor and accomplish the same thing.

tvanfosson
+6  A: 

There is quite a big difference.

The visitor pattern uses interfaces, but its purpose is to be able to perform an operation to one or more classes (who implement an interface) without having to change the classes. Hence, the implementation actually "visits" the class and does its thing without the class being modified.

An interface is a basic concept used to provide a common API to a potentially diverse group of classes. The typical test for an interface is that classes that share it are alike in at least that one respect (is-like-a) and in those cases can be treated as such.

Here is a simple example on wikipedia that shows a couple of visitors in java.

AdamC
The referenced code is almost exactly Visitor except that the interface is passed in, stored, then used later. If the interface had been passed to the perform() method and invoked from there it would be Visitor. I agree that interfaces and Visitor are different but this code is close to Visitor.
tvanfosson
+2  A: 

Two things:

  • In your example you need two methods. The perfom and the setInterface. With a visitor pattern you would only need one method, the perfom, usually called accept.
  • If you need more than one 'performer', you will have to set the performer -via the setInterface method- for each. This makes it impossible to make your class immutable.
Bno
+3  A: 

The most important difference in these examples is that in the visitor case you retain the compile-time concrete type of "this". This allows you to use double dispatch, where the method to be called is dependent on both the concrete data type and the visitor implementation. Double dispatch is just a special case of multiple dispatch where the method invoked is dependent on the receiver and the types of the parameters to the method. Java is of course single dispatch but some other languages support multiple dispatch.

The basic driving force behind the visitor pattern is that by using interfaces on the concrete nodes, every operation that needs to be added to a composite data structure must change every node. The visitor pattern uses a generic (static) pattern on the nodes so that dynamically adding operations is easy. The downside is that modifying the data structure (by adding or removing concrete nodes) becomes more difficult as all operation visitors are affected.

In general, this trade=off is a better match as it's more frequent to extend operations over a data structure than to change the data structure itself. Here's a lengthier writing of mine on how to use visitors and a bunch of considerations:

You might fairly ask if there is a pattern that allows us to do both: add operations or extend our data structures without breaking existing code. This is known as The Expression Problem as coined by Philip Wadler. You can find some links on this and more here:

Alex Miller
+1  A: 

A Visitor pattern is used when you have a data structure made up of many different classes and you have multiple algorithms that require a different operation for each class. In your example your DoInterface implementation only does one operation on one type. The only thing you do is print the result of getS() and because you cast o to T you can only do this to classes of type T.

If you wanted to apply your interface to a typical visitor style class you your the class with your DoInterface.perform function would likely end up with a big if else if statement in it something like this:

 public void visit(Object o) {
  if (o instanceof File)
   visitFile((File)o);
  else if (o instanceof Directory)
   visitDirectory((Directory)o);
  else if (o instanceof X)
   // ...
 }

Because this uses Object it will allow callers with any type which can create errors which will only show up at runtime. A Visitor gets around this by creating a “visitType” function for each type in the data structure. The classes in the data structure are then responsible for knowing which function on the visitor to call. The mapping is performed by each of the data structure’s classes implementing an accept function that then calls back on the Visitor class. If the function for the type does not exist on the visitor you get a compile error. The accept method looks like this:

 @Override
 public void accept(FileSystemVisitor v) {
  v.visitFile(this);
 }

Part of the trouble with the Visitor pattern is that it takes quite a lot of code to really do it justice in a sample. I think this is why a lot of people don't get it as it is easy to get distracted by the other code. I have created a simple file system sample that hopefully shows how to use a visitor more clearly. It creates a composite with some files and directories in and then performs two operations on the hierarchy. In practice you would probably want more than two data classes and two operations to justify this pattern but this is only an example.

public class VisitorSample {
    //
     public abstract class FileSystemItem {
      public abstract String getName();
      public abstract int getSize();
      public abstract void accept(FileSystemVisitor v);
     }
    //  
     public abstract class FileSystemItemContainer extends FileSystemItem {
      protected java.util.ArrayList<FileSystemItem> _list = new java.util.ArrayList<FileSystemItem>();
    //        
      public void addItem(FileSystemItem item)
      {
       _list.add(item);
      }
    //
      public FileSystemItem getItem(int i)
      {
       return _list.get(i);
      }
    //    
      public int getCount() {
       return _list.size();
      }
    //   
      public abstract void accept(FileSystemVisitor v);
      public abstract String getName();
      public abstract int getSize();
     }
    //  
     public class File extends FileSystemItem {
    //
      public String _name;
      public int _size;
    //   
      public File(String name, int size) {
       _name = name;
       _size = size;
      }
    //   
      @Override
      public void accept(FileSystemVisitor v) {
       v.visitFile(this);
      }
    //
      @Override
      public String getName() {
       return _name;
      }
    //
      @Override
      public int getSize() {
       return _size;
      }
     }
    //  
     public class Directory extends FileSystemItemContainer {
    //
      private String _name;
    //   
      public Directory(String name) {
       _name = name;
      }
    //   
      @Override
      public void accept(FileSystemVisitor v) {
       v.visitDirectory(this);
      }
    //
      @Override
      public String getName() {
       return _name;
      }
    //
      @Override
      public int getSize() {
       int size = 0;
       for (int i = 0; i < _list.size(); i++)
       {
        size += _list.get(i).getSize();
       }
       return size;
      }  
     }
    //  
     public abstract class FileSystemVisitor {
    //   
      public void visitFile(File f) { }
      public void visitDirectory(Directory d) { }
    //
      public void vistChildren(FileSystemItemContainer c) {
       for (int i = 0; i < c.getCount(); i++)
       {
        c.getItem(i).accept(this);
       }
      }
     }
    //  
     public class ListingVisitor extends FileSystemVisitor {
    //   
      private int _indent = 0;
    //   
      @Override
      public void visitFile(File f) {
       for (int i = 0; i < _indent; i++)
        System.out.print(" ");
       System.out.print("~");
       System.out.print(f.getName());
       System.out.print(":");
       System.out.println(f.getSize());
      }
    //  
      @Override
      public void visitDirectory(Directory d) {
       for (int i = 0; i < _indent; i++)
        System.out.print(" "); 
       System.out.print("\\");
       System.out.print(d.getName());
       System.out.println("\\");
    //    
       _indent += 3;
       vistChildren(d);
       _indent -= 3;
      }
     }
    //  
     public class XmlVisitor extends FileSystemVisitor {
    //   
      private int _indent = 0;
    //   
      @Override
      public void visitFile(File f) {
       for (int i = 0; i < _indent; i++)
        System.out.print(" ");
       System.out.print("<file name=\"");
       System.out.print(f.getName());
       System.out.print("\" size=\"");
       System.out.print(f.getSize());
       System.out.println("\" />");
      }
    //  
      @Override
      public void visitDirectory(Directory d) {
       for (int i = 0; i < _indent; i++)
        System.out.print(" ");
       System.out.print("<directory name=\"");
       System.out.print(d.getName());
       System.out.print("\" size=\"");
       System.out.print(d.getSize());
       System.out.println("\">");
    //    
       _indent += 4;
       vistChildren(d);
       _indent -= 4;
    //    
       for (int i = 0; i < _indent; i++)
        System.out.print(" ");
       System.out.println("</directory>");
      }
     }
    //  
     public static void main(String[] args) {
      VisitorSample s = new VisitorSample();
    //   
      Directory root = s.new Directory("root");
      root.addItem(s.new File("FileA", 163));
      root.addItem(s.new File("FileB", 760));
      Directory sub = s.new Directory("sub");
      root.addItem(sub);
      sub.addItem(s.new File("FileC", 401));
      sub.addItem(s.new File("FileD", 543));
      Directory subB = s.new Directory("subB");
      root.addItem(subB);
      subB.addItem(s.new File("FileE", 928));
      subB.addItem(s.new File("FileF", 238));
    //   
      XmlVisitor xmlVisitor = s.new XmlVisitor();
      root.accept(xmlVisitor);
    //   
      ListingVisitor listing = s.new ListingVisitor();
      root.accept(listing);
     }
    }
Martin Brown