Applied Design Patterns with Java
Behavioral :: Visitor (331) {C ch 26}
Implementation
Each object structure will have an associated Visitor
class. This abstract Visitor class declares a VisitConcreteElement operation for each class of ConcreteElement
defining the object structure. Each Visit operation on the Visitor declares its argument to be a particular ConcreteElement, allowing
the Visitor to access the interface of the ConcreteElement directly. ConcreteVisitor classes override
each Visit operation to implement visitor-specific behavior for the corresponding ConcreteElement class.
Two other implementation issues that arise when applying the Visitor pattern:
- Double dispatch. The Visitor pattern allows adding operations to classes without changing them.
Visitor achieves this by using a technique called double-dispatch. It's a well-known technique. In fact, some programming languages
support it directly (CLOS, for example). Languages like C++ and Java support single-dispatch.
In single-dispatch languages, two criteria determine which operation will fulfill a request: the name of the request
and the type of receiver. For example, the operation that a GenerateCode request will call depends on the type
of node object asked for. The operation that gets executed depends both on the kind of request and the type of
the receiver. "Double-dispatch" simply means the operation that gets executed depends on the kind
of request and the types of two receivers. Accept is a double-dispatch operation. Its meaning depends on two types:
the Visitor's and the Element's. Double-dispatching lets visitors request different operations on
each class of element. This is the key to the Visitor pattern:
The operation that gets executed depends on both the type of Visitor and the type of Element it visits. Instead of binding operations statically into the Element interface, consolidate the operations
in a Visitor and use Accept to do the binding at run-time. Extending the Element interface amounts
to defining one new Visitor subclass rather than many new Element subclasses.
- Who is responsible for traversing the object structure?
A Visitor must visit each element of the object structure. The question is,
how does it get there? The responsibility for traversal can be put in any of three places: in the object structure,
in the Visitor, or in a separate Iterator
(257) object. Often the object structure is responsible
for iteration. A collection will simply iterate over its elements, calling the Accept operation on each. A Composite
(163)
will commonly traverse itself by having each Accept operation traverse the element's children and call Accept on
each of them recursively.
Another solution is to use an Iterator to visit the elements, either an internal or external iterator,
depending on what is available and what is most efficient. Since internal Iterators are implemented
by the object structure, using an internal Iterator is a lot like making the object structure responsible for iteration.
The main difference is that an internal Iterator will not cause double-dispatching it will call an operation on the Visitor with an element
as an argument, as opposed to calling an operation on the element with the Visitor as an argument.
But it's easy to use the Visitor pattern with an internal Iterator if the operation
on the Visitor simply calls the operation on the element without recursing.
It is even possible to put the traversal algorithm in the Visitor, although this will duplicate the traversal code in each ConcreteVisitor
for each aggregate ConcreteElement. The main reason to put the traversal strategy in the Visitor
is to implement a particularly complex traversal, one that depends on the results of the operations on the object
structure.
Related Patterns
Composite (163): Visitors can be used to apply an operation over an object structure defined by the
Composite pattern.
Interpreter (243): Visitor may be applied to do the interpretation.
Iterator (257): Visitors are often used with the Iterator pattern.
Catalog Behavioral Prev Next