Java Design Patterns – Understanding the Need for Visitor Pattern

design-patternsjava

After seeing an article on visitor pattern, it is clear to me how it works. And I created a sample program for my understanding;

main(){
    SortingAlgorithm bubbleSort;
    :
    intList.sort(bubbleSort);
}

class IntList implements Sortable{
    :

    public void sort(SortingAlgorithm algo){
        return algo.sort(this);
    }
}

interface SortingAlgorithm{
    public void sort(IntList list);
    public void sort(DoubleList list);
    public void sort(LongList list);
}

class BubbleSort implements SortingAlgorithm{

    public void sort(IntList list){
    :
    }
    public void sort(DoubleList list){
        :
    }
    public void sort(LongList list){
        :
    }
}

And the advantage I can see is that if I implement new algorithm, I need not to modify IntList;

main(){
    SortingAlgorithm heapSort;
    :
    intList.sort(heapSort);
}

class HeapSort implements SortingAlgorithm{

    public void sort(IntList arr){
    :
    }
    public void sort(DoubleList list){
        :
    }
    public void sort(LongList list){
        :
    }
}

The other thing I can see is that if I create another type of list or map, say IntMap, then I have to just add a method in Sortable interface and need to implement it in all the relevant classes: BubbleSort, HeapSort.

Now what I can't understand is the need of visitor patterns as I can simplify above program like this;

main(){
    BubbleSort.sort(intList);

}

Now my primitives list and maps need not to implement any interface and it's method. And even SortingAlgorithm interface is not required anymore.

Best Answer

The visitor pattern is useful when you want to process a data structure containing different kinds of objects, and you want to perform a specific operation on each of them, depending on its type.

Your example is not the best, since you pass a single homogeneous list as input, so there is really no need for the pattern. As you point out, you can just call the appropriate method directly. (Furthermore a sorting algorithm shouldn't even care it the input is a list of ints or doubles, as long as all items are of the same type, and the type has a comparison function.)

But consider if you have a directory tree, and you want to display the first few lines of text from each file. This would require different logic if the file was plaintext, HTML, Word or PDF. So this would be appropriate use of the visitor pattern since you want a generic way to traverse the tree, but you want the preview of each leaf to be handled depending on its type.

Example

We have PdfFile, HtmlFile and TextFile which all are descendants of the abstract class File. Assume the method dir("path") has the type List<File> but returns instances of PdfFile, HtmlFile etc.

We create a class Head which is supposed to generate the summaries for these different file types, so it will have this interface:

class Head {
  static String of(PdfFile file) { ... }
  static String of(HtmlFile file) { ... }
  static String of(TextFile file) { ... }
}

Then you might be tempted to do something like this:

dir("path").forEach(file -> Head.of(file)))

The problem is this doesn't work! In Java and similar languages, method overloads based on parameter type are resolved at compile time, not runtime. Since the type of the list is something like List<File>, the compiler will look for a single method with the signature of(File file). How do we solve this problem? We could add an override to every subclass of File which looks like this:

public override string acceptHead() { return Head.of(this); }

And then do this:

dir("path").forEach(file -> file.acceptHead()))

This actually works, because dispatch on the instance (before the dot) is resolved at runtime, so we get the correct override which in turn calls the correct overload of of. Then you could generalize the acceptHead method to just accept, and create a generalized FileVisitor interface, which Head is just an instance of, so you can reuse the dispatch logic for other purposes. And now you have a visitor pattern.

In some ways, the visitor pattern is simply a workaround for the fact that Java (and similar languages) are single dispatch, which means overrides are selected at runtime only based on the instance before the dot. If overloads also was selected at runtime based on the parameter types, then you wouldn't need this pattern.

Related Topic