Visitor pattern
A visitor pattern is a software design pattern that separates the algorithm from the object structure. Because of this separation, new operations can be added to existing object structures without modifying the structures. It is one way to follow the open/closed principle in object-oriented programming and software engineering. 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. Programming languages with sum types and pattern matching obviate many of the benefits of the visitor pattern, as the visitor class is able to both easily branch on the type of the object and generate a compiler error if a new object type is defined which the visitor does not yet handle. OverviewThe Visitor[1] design pattern is one of the twenty-three well-known Gang of Four 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?
When new operations are needed frequently and the object structure consists of many unrelated 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."[1] What solution does the Visitor design pattern describe?
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. DefinitionThe 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.[2] AdvantagesMoving operations into visitor classes is beneficial when
A drawback to this pattern, however, is that it makes extensions to the class hierarchy more difficult, as new classes typically require a new ApplicationConsider the design of a 2D computer-aided design (CAD) 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. As the versions of the same file grows it becomes more complicated to maintain it. Instead, the visitor pattern can be applied. It encodes the logical operation (i.e. save(image_tree)) on the whole hierarchy into one class (i.e. Saver) that implements the common methods for traversing the tree and describes virtual helper methods (i.e. save_circle, save_square, etc.) to be implemented for format specific behaviors. In the case of the CAD example, such format specific behaviors would be implemented by a subclass of Visitor (i.e. SaverPNG). As such, all duplication of type checks and traversal steps is removed. Additionally, the compiler now complains if a shape is omitted since it is now expected by the common base traversal/save function. Iteration loopsThe visitor pattern may be used for iteration over container-like data structures just like Iterator pattern but with limited functionality.[3]: 288 For example, iteration over a directory structure could be implemented by a function class instead of more conventional loop pattern. This would allow deriving various useful information from directories content by implementing a visitor functionality for every item while reusing the iteration code. It's widely employed in Smalltalk systems and can be found in C++ as well.[3]: 289 A drawback of this approach, however, is that you can't break out of the loop easily or iterate concurrently (in parallel i.e. traversing two containers at the same time by a single StructureUML class and sequence diagram![]() In the UML class diagram above, the The UML sequence diagram
shows the run-time interactions: The Class diagram![]() ![]() DetailsThe visitor pattern requires a programming language that supports single dispatch, as common object-oriented languages (such as C++, Java, Smalltalk, Objective-C, Swift, JavaScript, Python and C#) 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 The element declares an 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 When the Thus, the implementation of the 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# exampleThis example declares a separate using System;
namespace Wikipedia;
public interface Visitor
{
void Visit(Literal literal);
void Visit(Addition addition);
}
public class ExpressionPrintingVisitor : Visitor
{
public void Visit(Literal literal)
{
Console.WriteLine(literal.Value);
}
public void Visit(Addition addition)
{
double leftValue = addition.Left.GetValue();
double rightValue = addition.Right.GetValue();
var sum = addition.GetValue();
Console.WriteLine($"{leftValue} + {rightValue} = {sum}");
}
}
public abstract class Expression
{
public abstract void Accept(Visitor v);
public abstract double GetValue();
}
public class Literal : Expression
{
public Literal(double value)
{
this.Value = value;
}
public double Value { get; set; }
public override void Accept(Visitor v)
{
v.Visit(this);
}
public override double GetValue()
{
return Value;
}
}
public class Addition : Expression
{
public Addition(Expression left, Expression right)
{
Left = left;
Right = right;
}
public Expression Left { get; set; }
public Expression Right { get; set; }
public override void Accept(Visitor v)
{
Left.Accept(v);
Right.Accept(v);
v.Visit(this);
}
public override double GetValue()
{
return Left.GetValue() + Right.GetValue();
}
}
public static class Program
{
public static void Main(string[] args)
{
// Emulate 1 + 2 + 3
var e = new Addition(
new Addition(
new Literal(1),
new Literal(2)
),
new Literal(3)
);
var printingVisitor = new ExpressionPrintingVisitor();
e.Accept(printingVisitor);
Console.ReadKey();
}
}
Smalltalk exampleIn 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: $(.
left putOn: aStream.
aStream nextPut: $+.
right putOn: aStream.
aStream nextPut: $).
Object subclass: #Program
instanceVariableNames: ''
classVariableNames: ''
package: 'Wikipedia'.
Program>>main
| expression stream |
expression := Addition
left: (Addition
left: (Literal with: 1)
right: (Literal with: 2))
right: (Literal with: 3).
stream := ExpressionPrinter on: (String new: 100).
stream write: expression.
Transcript show: stream contents.
Transcript flush.
GoGo does not support method overloading, so the visit methods need different names. A typical visitor interface might be type Visitor interface {
visitWheel(wheel Wheel) string
visitEngine(engine Engine) string
visitBody(body Body) string
visitCar(car Car) string
}
Java exampleThe following example is in the language Java, and shows how the contents of a tree of nodes (in this case describing the components of a car) can be printed. Instead of creating DiagramSourcesimport java.util.List;
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Wheel implements CarElement {
private final String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CarElementVisitor visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the *first* dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the *second* dispatch.
*/
visitor.visit(this);
}
}
class Body implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Car implements CarElement {
private final List<CarElement> elements;
public Car() {
this.elements = List.of(
new Wheel("front left"), new Wheel("front right"),
new Wheel("back left"), new Wheel("back right"),
new Body(), new Engine()
);
}
@Override
public void accept(CarElementVisitor visitor) {
for (CarElement element : elements) {
element.accept(visitor);
}
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Moving my body");
}
@Override
public void visit(Car car) {
System.out.println("Starting my car");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
@Override
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Visiting body");
}
@Override
public void visit(Car car) {
System.out.println("Visiting car");
}
@Override
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
OutputVisiting 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 exampleSources(defclass auto ()
((elements :initarg :elements)))
(defclass auto-part ()
((name :initarg :name :initform "<unnamed-car-part>")))
(defmethod print-object ((p auto-part) stream)
(print-object (slot-value p 'name) stream))
(defclass wheel (auto-part) ())
(defclass body (auto-part) ())
(defclass engine (auto-part) ())
(defgeneric traverse (function object other-object))
(defmethod traverse (function (a auto) other-object)
(with-slots (elements) a
(dolist (e elements)
(funcall function e other-object))))
;; do-something visitations
;; catch all
(defmethod do-something (object other-object)
(format t "don't know how ~s and ~s should interact~%" object other-object))
;; visitation involving wheel and integer
(defmethod do-something ((object wheel) (other-object integer))
(format t "kicking wheel ~s ~s times~%" object other-object))
;; visitation involving wheel and symbol
(defmethod do-something ((object wheel) (other-object symbol))
(format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object))
(defmethod do-something ((object engine) (other-object integer))
(format t "starting engine ~s ~s times~%" object other-object))
(defmethod do-something ((object engine) (other-object symbol))
(format t "starting engine ~s symbolically using symbol ~s~%" object other-object))
(let ((a (make-instance 'auto
:elements `(,(make-instance 'wheel :name "front-left-wheel")
,(make-instance 'wheel :name "front-right-wheel")
,(make-instance 'wheel :name "rear-left-wheel")
,(make-instance 'wheel :name "rear-right-wheel")
,(make-instance 'body :name "body")
,(make-instance 'engine :name "engine")))))
;; traverse to print elements
;; stream *standard-output* plays the role of other-object here
(traverse #'print a *standard-output*)
(terpri) ;; print newline
;; traverse with arbitrary context from other object
(traverse #'do-something a 42)
;; traverse with arbitrary context from other object
(traverse #'do-something a 'abc))
Output"front-left-wheel" "front-right-wheel" "rear-left-wheel" "rear-right-wheel" "body" "engine" kicking wheel "front-left-wheel" 42 times kicking wheel "front-right-wheel" 42 times kicking wheel "rear-left-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-left-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 NotesThe (defmethod traverse (function (a auto)) ;; other-object removed
(with-slots (elements) a
(dolist (e elements)
(funcall function e)))) ;; from here too
;; ...
;; alternative way to print-traverse
(traverse (lambda (o) (print o *standard-output*)) a)
;; alternative way to do-something with
;; elements of a and integer 42
(traverse (lambda (o) (do-something o 42)) a)
Now, the multiple dispatch occurs in the call issued from the body of the anonymous function, and so Python examplePython does not support method overloading in the classical sense (polymorphic behavior according to type of passed parameters), so the "visit" methods for the different model types need to have different names. Sources"""
Visitor pattern example.
"""
from abc import ABCMeta, abstractmethod
NOT_IMPLEMENTED = "You should implement this."
class CarElement(metaclass=ABCMeta):
@abstractmethod
def accept(self, visitor):
raise NotImplementedError(NOT_IMPLEMENTED)
class Body(CarElement):
def accept(self, visitor):
visitor.visitBody(self)
class Engine(CarElement):
def accept(self, visitor):
visitor.visitEngine(self)
class Wheel(CarElement):
def __init__(self, name):
self.name = name
def accept(self, visitor):
visitor.visitWheel(self)
class Car(CarElement):
def __init__(self):
self.elements = [
Wheel("front left"), Wheel("front right"),
Wheel("back left"), Wheel("back right"),
Body(), Engine()
]
def accept(self, visitor):
for element in self.elements:
element.accept(visitor)
visitor.visitCar(self)
class CarElementVisitor(metaclass=ABCMeta):
@abstractmethod
def visitBody(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitEngine(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitWheel(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
@abstractmethod
def visitCar(self, element):
raise NotImplementedError(NOT_IMPLEMENTED)
class CarElementDoVisitor(CarElementVisitor):
def visitBody(self, body):
print("Moving my body.")
def visitCar(self, car):
print("Starting my car.")
def visitWheel(self, wheel):
print("Kicking my {} wheel.".format(wheel.name))
def visitEngine(self, engine):
print("Starting my engine.")
class CarElementPrintVisitor(CarElementVisitor):
def visitBody(self, body):
print("Visiting body.")
def visitCar(self, car):
print("Visiting car.")
def visitWheel(self, wheel):
print("Visiting {} wheel.".format(wheel.name))
def visitEngine(self, engine):
print("Visiting engine.")
car = Car()
car.accept(CarElementPrintVisitor())
car.accept(CarElementDoVisitor())
OutputVisiting 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.
AbstractionUsing Python 3 or above allows to make a general implementation of the accept method: class Visitable:
def accept(self, visitor):
lookup = "visit_" + self.__qualname__.replace(".", "_")
return getattr(visitor, lookup)(self)
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
See alsoReferences
External linksWikimedia Commons has media related to Visitor pattern. The Wikibook Computer Science Design Patterns has a page on the topic of: Visitor implementations in various languages
|
Portal di Ensiklopedia Dunia