Visitor design pattern
The visitor design pattern provides a method of separating an algorithm on an object and the object’s actual class implementation. This allows the programmer to easily follow the open/closed principle.
The open/closed principle states;
software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification
That is, modifying an entity’s behaviour without modifying the underlying source code. Following the open/closed principle provides many quality-related benefits as the original code never changes.
Benefits
- Follows the open/closed principle
- Allows a new operation to be defined without changing the implementation of the class
- A visitor object can have state
Drawbacks
- If a new visitable object is added then all visitors need to be modified
Double dispatch
The visitor pattern is one way of implementing double dispatch in a language that doesn’t support it. Double dispatch is a method of dispatching a function call to a particular concrete function based on two object parameters. To understand this better, it’s basically a method of implementing the following:
(A, B).func();
It may seem like function overloading can accomplish this same task, and in some cases that is true. However, properly implementing double dispatch solves a problem that can occur due to the compile-time evaluation of calls. For example, consider this code:
class Paint { }
class RedPaint extends Paint { }
class Car {
public void paint(Paint paint) {
System.out.println("Car painted");
}
public void paint(RedPaint paint) {
System.out.println("Car painted red");
}
}
public class Program {
public static void main(String[] args) {
Car car = new Car();
Paint paint = new Paint();
Paint redPaint = new RedPaint();
car.paint(paint);
car.paint(redPaint);
}
}
The output for the above program is not as expected:
Car painted Car painted
The second line should say Car painted red
since RedPaint
is used but instead uses the implementation of its super type Paint
. This is because the function overloading on Car.paint(...)
is done at compile-time and uses the redPaint
variable’s boxed type Paint
.
To solve this problem the visitor pattern can be applied which has a single method Car.paint(Paint)
that calls the Paint.paint(Car)
interface. The implementation chosen depends on what the type is at runtime, not compile-time.
class Car {
public void paint(Paint paint) {
paint.paint(this);
}
}
class Paint {
public void paint(Car car) {
System.out.println("Car painted");
}
}
class RedPaint extends Paint {
public void paint(Car car) {
System.out.println("Car painted red");
}
}
Producing the expected output:
Car painted red FastCar painted red
This works because instead of relying on function overloading on Car.paint(Paint)
to dispatch the call which is evaluated statically at compile-time, the function Paint.paint(Car)
is used which is virtual and evaluated dynamically at runtime.
UML diagram
Code
public interface ElementInterface {
public void accept(VisitorInterface visitor);
}
public class ConcreteElementA implements ElementInterface {
private String lastVisitedBy;
@Override
public void accept(VisitorInterface visitor) {
visitor.visit(this);
}
public String getLastVisitedBy() { return lastVisitedBy; }
public void setLastVisitedBy(String value) { lastVisitedBy = value; }
}
public class ConcreteElementB implements ElementInterface {
private String lastVisitedBy;
@Override
public void accept(VisitorInterface visitor) {
visitor.visit(this);
}
public String getLastVisitedBy() { return lastVisitedBy; }
public void setLastVisitedBy(String value) { lastVisitedBy = value; }
}
public interface VisitorInterface {
public void visit(ConcreteElementA element);
public void visit(ConcreteElementB element);
}
public class ConcreteVisitor1 implements VisitorInterface {
@Override
public void visit(ConcreteElementA element) {
element.setLastVisitedBy("ConcreteVisitor1");
}
@Override
public void visit(ConcreteElementB element) {
element.setLastVisitedBy("ConcreteVisitor1");
}
}
public class ConcreteVisitor2 implements VisitorInterface {
@Override
public void visit(ConcreteElementA element) {
element.setLastVisitedBy("ConcreteVisitor2");
}
@Override
public void visit(ConcreteElementB element) {
element.setLastVisitedBy("ConcreteVisitor2");
}
}
// file: element.js
'use strict';
var Element = function () {
this.lastVisitedBy = undefined;
};
Element.prototype.accept = function (visitor) {
visitor.visit(this);
};
module.exports = Element;
// file: visitor-a.js
'use strict';
var VisitorA = function () { };
VisitorA.prototype.visit = function (element) {
element.lastVisitedBy = 'VisitorA';
};
module.exports = VisitorA;
// file: visitor-b.js
'use strict';
var VisitorB = function () { };
VisitorB.prototype.visit = function (element) {
element.lastVisitedBy = 'VisitorB';
};
module.exports = VisitorB;
Usage examples
Here are a few example of where you could use the visitor pattern:
- A ‘screen painter’ object (the visitor) that paints several widgets to the screen (the elements).
- A HTML parser (visitor) that parses HTML nodes (the elements).