Visitor pattern
In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. It is one way to follow the open/closed principle.
In essence, the visitor allows adding new virtual functions to a family of classes, without modifying the classes. Instead, a visitor class is created that implements all of the appropriate specializations of the virtual function. The visitor takes the instance reference as input, and implements the goal through double dispatch.
Overview
The Visitordesign pattern is one of the twenty-three well-known GoF design patterns
that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is,
objects that are easier to implement, change, test, and reuse.
What problems can the Visitor design pattern solve?
- It should be possible to define a new operation for classes of an object structure without changing the classes.
it's inflexible to add new subclasses each time a new operation is required
because " distributing all these operations across the various node classes leads to a system that's hard to understand, maintain, and change."
What solution does the Visitor design pattern describe?
- Define a separate object that implements an operation to be performed on elements of an object structure.
- Clients traverse the object structure and call a dispatching operation accept on an element — that "dispatches" the request to the "accepted visitor object". The visitor object then performs the operation on the element.
This makes it possible to create new operations independently from the classes of an object structure
by adding new visitor objects.
See also the UML class and sequence diagram below.
Definition
The Gang of Four defines the Visitor as:The nature of the Visitor makes it an ideal pattern to plug into public APIs thus allowing its clients to perform operations on a class using a "visiting" class without having to modify the source.
Uses
Moving operations into visitor classes is beneficial when- many unrelated operations on an object structure are required,
- the classes that make up the object structure are known and not expected to change,
- new operations need to be added frequently,
- an algorithm involves several classes of the object structure, but it is desired to manage it in one single location,
- an algorithm needs to work across several independent class hierarchies.
visit
method to be added to each visitor.Use case example
Consider the design of a 2D computer-aided design system. At its core there are several types to represent basic geometric shapes like circles, lines, and arcs. The entities are ordered into layers, and at the top of the type hierarchy is the drawing, which is simply a list of layers, plus some added properties.A fundamental operation on this type hierarchy is saving a drawing to the system's native file format. At first glance it may seem acceptable to add local save methods to all types in the hierarchy. But it is also useful to be able to save drawings to other file formats. Adding ever more methods for saving into many different file formats soon clutters the relatively pure original geometric data structure.
A naive way to solve this would be to maintain separate functions for each file format. Such a save function would take a drawing as input, traverse it, and encode into that specific file format. As this is done for each added different format, duplication between the functions accumulates. For example, saving a circle shape in a raster format requires very similar code no matter what specific raster form is used, and is different from other primitive shapes. The case for other primitive shapes like lines and polygons is similar. Thus, the code becomes a large outer loop traversing through the objects, with a large decision tree inside the loop querying the type of the object. Another problem with this approach is that it is very easy to miss a shape in one or more savers, or a new primitive shape is introduced, but the save routine is implemented only for one file type and not others, leading to code extension and maintenance problems.
Instead, the visitor pattern can be applied. It encodes a logical operation on the whole hierarchy into one class containing one method per type. In the CAD example, each save function would be implemented as a separate Visitor subclass. This would remove all duplication of type checks and traversal steps. It would also make the compiler complain if a shape is omitted.
Another motive is to reuse iteration code. For example, iterating over a directory structure could be implemented with a visitor pattern. This would allow creating file searches, file backups, directory removal, etc., by implementing a visitor for each function while reusing the iteration code.
Structure
UML class and sequence diagram
In the above UML class diagram, theElementA
class doesn't implement a new operation directly.Instead,
ElementA
implements a dispatching operation accept
that "dispatches" a request to the "accepted visitor object". The Visitor1
class implements the operation.ElementB
then implements accept
by dispatching to visitor.visitElementB
. The Visitor1
class implements the operation.The UML sequence diagram
shows the run-time interactions: The
Client
object traverses the elements of an object structure and calls accept
on each element.First, the
Client
calls accept
onElementA
, which calls visitElementA
on the accepted visitor
object.The element itself is passed to the
visitor
so that it can "visit"
ElementA
.Thereafter, the
Client
calls accept
onElementB
, which calls visitElementB
on the visitor
that "visits" ElementB
.Class diagram
Details
The visitor pattern requires a programming language that supports single dispatch, as common object-oriented languages do. Under this condition, consider two objects, each of some class type; one is termed the element, and the other is visitor.The visitor declares a
visit
method, which takes the element as an argument, for each class of element. Concrete visitors are derived from the visitor class and implement these visit
methods, each of which implements part of the algorithm operating on the object structure. The state of the algorithm is maintained locally by the concrete visitor class.The element declares an
accept
method to accept a visitor, taking the visitor as an argument. Concrete elements, derived from the element class, implement the accept
method. In its simplest form, this is no more than a call to the visitor's visit
method. Composite elements, which maintain a list of child objects, typically iterate over these, calling each child's accept
method.The client creates the object structure, directly or indirectly, and instantiates the concrete visitors. When an operation is to be performed which is implemented using the Visitor pattern, it calls the
accept
method of the top-level element.When the
accept
method is called in the program, its implementation is chosen based on both the dynamic type of the element and the static type of the visitor. When the associated visit
method is called, its implementation is chosen based on both the dynamic type of the visitor and the static type of the element, as known from within the implementation of the accept
method, which is the same as the dynamic type of the element. Thus, the implementation of the
visit
method is chosen based on both the dynamic type of the element and the dynamic type of the visitor. This effectively implements double dispatch. For languages whose object systems support multiple dispatch, not only single dispatch, such as Common Lisp or C# via the Dynamic Language Runtime, implementation of the visitor pattern is greatly simplified by allowing use of simple function overloading to cover all the cases being visited. A dynamic visitor, provided it operates on public data only, conforms to the open/closed principle and to the single responsibility principle.In this way, one algorithm can be written to traverse a graph of elements, and many different kinds of operations can be performed during that traversal by supplying different kinds of visitors to interact with the elements based on the dynamic types of both the elements and the visitors.
C# example
This example shows how to print a tree representing a numeric expression involving literals and their addition. The same example is presented using both classic and Dynamic Language Runtime implementations.Dynamic visitor
This example declares a separateExpressionPrinter
class that takes care of the printing. The expression classes must expose their members to make this possible.using System;
using System.Text;
namespace Wikipedia
Smalltalk example
In this case, it is the object's responsibility to know how to print itself on a stream. The visitor here is then the object, not the stream."There's no syntax for creating a class. Classes are created by sending messages to other classes."
WriteStream subclass: #ExpressionPrinter
instanceVariableNames:
classVariableNames:
package: 'Wikipedia'.
ExpressionPrinter>>write: anObject
"Delegates the action to the object. The object doesn't need to be of any special
class; it only needs to be able to understand the message #putOn:"
anObject putOn: self.
^ anObject.
Object subclass: #Expression
instanceVariableNames:
classVariableNames:
package: 'Wikipedia'.
Expression subclass: #Literal
instanceVariableNames: 'value'
classVariableNames:
package: 'Wikipedia'.
Literal class>>with: aValue
"Class method for building an instance of the Literal class"
^ self new
value: aValue;
yourself.
Literal>>value: aValue
"Setter for value"
value := aValue.
Literal>>putOn: aStream
"A Literal object knows how to print itself"
aStream nextPutAll: value asString.
Expression subclass: #Addition
instanceVariableNames: 'left right'
classVariableNames:
package: 'Wikipedia'.
Addition class>>left: a right: b
"Class method for building an instance of the Addition class"
^ self new
left: a;
right: b;
yourself.
Addition>>left: anExpression
"Setter for left"
left := anExpression.
Addition>>right: anExpression
"Setter for right"
right := anExpression.
Addition>>putOn: aStream
"An Addition object knows how to print itself"
aStream nextPut: $.
Object subclass: #Program
instanceVariableNames:
classVariableNames:
package: 'Wikipedia'.
Program>>main
| expression stream |
expression := Addition
left:
right: )
right:.
stream := ExpressionPrinter on:.
stream write: expression.
Transcript show: stream contents.
Transcript flush.
C++ example
Output
dispatching ArchivedFile
dispatching SplitFile
dispatching ExtractedFile
Go example
Go does not support overloading, so the visit methods need different names.Output
visiting car
visiting engine
visiting body
visiting front left wheel
visiting front right wheel
visiting back left wheel
visiting back right wheel
Java example
The following example is in the language Java, and shows how the contents of a tree of nodes can be printed. Instead of creatingprint
methods for each node subclass, one visitor class performs the required printing action. Because different node subclasses require slightly different actions to print properly, CarElementPrintVisitor
dispatches actions based on the class of the argument passed to its visit
method. CarElementDoVisitor
, which is analogous to a save operation for a different file format, does likewise.Diagram
Output
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car
Common Lisp example
Output
"front-left-wheel"
"front-right-wheel"
"rear-right-wheel"
"rear-right-wheel"
"body"
"engine"
kicking wheel "front-left-wheel" 42 times
kicking wheel "front-right-wheel" 42 times
kicking wheel "rear-right-wheel" 42 times
kicking wheel "rear-right-wheel" 42 times
don't know how "body" and 42 should interact
starting engine "engine" 42 times
kicking wheel "front-left-wheel" symbolically using symbol ABC
kicking wheel "front-right-wheel" symbolically using symbol ABC
kicking wheel "rear-right-wheel" symbolically using symbol ABC
kicking wheel "rear-right-wheel" symbolically using symbol ABC
don't know how "body" and ABC should interact
starting engine "engine" symbolically using symbol ABC
Python example
Python does not support method overloading in classical sense, so the "visit" methods for the different model types need to have different names.Output
Visiting front left wheel.
Visiting front right wheel.
Visiting back left wheel.
Visiting back right wheel.
Visiting body.
Visiting engine.
Visiting car.
Kicking my front left wheel.
Kicking my front right wheel.
Kicking my back left wheel.
Kicking my back right wheel.
Moving my body.
Starting my engine.
Starting my car.
Abstraction
If one is using Python 3 or above, they can make a general implementation of the accept method:class Visitable:
def accept:
lookup = "visit_" + type.__qualname__.replace
return getattr
One could extend this to iterate over the class's method resolution order if they would like to fall back on already-implemented classes. They could also use the subclass hook feature to define the lookup in advance.
Related design patterns
- Iterator pattern – defines a traversal principle like the visitor pattern, without making a type differentiation within the traversed objects
- Church encoding – a related concept from functional programming, in which tagged union/sum types may be modeled using the behaviors of "visitors" on such types, and which enables the visitor pattern to emulate variants and patterns.