Question

I know and use both iterators and visitors frequently, and have been using both before I even heard about Gang of Four's Design Patterns. Though the syntax is pretty different for the two patterns, I use both for the same conceptual goal: traversing a group of objects. Roughly speaking I use the iterator when I have unstructured objects of the same type, and I use the visitor when I have structured objects of different types. To me the visitor is just an elegant, fancy, and stronger typed iterator.

When I read Design Patterns i noticed the description of visitors and especially how it differs from the iterator description.

Visitor lets you define a new operation without changing the classes of the elements on which it operates

Iterator: Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.

I have thought about it since and I can't really figure out how the visitor defines a new operation.

If I for example want to implement a rather simple operation, like toLocalizedString() as a localized alternative to toString(). When you pass an visitor to an element, it will traverse the entire substructure of that object. Further you can't return anything from the accept/visit methods. Each of these characteristics prevents me from using a visitor to define toLocalizedString().

And this brings my question: How does a visitor "define a new operation" in a way that an iterator does not? If I am to believe Gang of Four's description, I feel like I'm missing out on the true power of the visitor pattern.

Was it helpful?

Solution

Traversal of structures is not the defining feature of the visitor pattern. In fact, one could think of your use of visitors to traverse objects with structures as a fancy iterator.

What differentiates visitors from iterators is that visitors let you do the so-called Double Dispatch, i.e. routing a message to a method that depends both on the runtime type of the object being visited and the visitor object. The object being visited is external to the visitor, but the executable code of the operation is contained inside the visitor. In other words, following the visitor pattern lets you perform operations that are external to the object being visited, which could be thought of as defining new operations on the object.

OTHER TIPS

You've identified the following as reasons why you can't introduce new operations with a visitor:

  1. "When you pass an visitor to an element, it will traverse the entire substructure of that object."
  2. "Further you can't return anything from the accept/visit methods."

I think that reason (1) is often a valid concern. However, I think that you can modify your visitor to get around this. Suppose that you make visit and accept return a boolean indicating whether the exploration of the structure should continue. You could then have accept work like this (in pseudocode):

 for (each child) {
     if (!child.accept(visitor)) return false;
 }
 return visitor.visit(this);

That way, as soon as the visitor recognizes that it's done, it can stop searching. This is similar to doing a depth-first, backtracking search of the objects linked together.

For (2), however, I think you're missing an important technique with visitors - since visitors are objects, they can contain private state information. For example, suppose that I want to write a simple count function that returns the total number of elements in the structure. Then I could write something like this:

public int count(Base object) {
    Visitor v = new Visitor() {
        private int numElems = 0;

        public void visit(Base object) {
            numElems++;
        }
        public void visit(Derived1 object) {
            numElems++;
        }
        // ... etc

        public int getCount() {
            return numElems;
        }
    };
    object.accept(v);
    return v.getCount();
}

Notice that even though visit and accept return void here, I can still build an overall method that returns a value by having the visitor maintain internal state and then returning that state once the visiting is completed. From now on, calling count(obj) wil; return a count of all objects, essentially "grafting" on an extra method to the class hierarchy.

Hope this helps!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top