Visitor is a behavioral pattern used to separate algorithm from the objects on which it operates. This tutorial explains visitor patten with Java examples.
What is Visitor Pattern?
Imagine you have a bunch of shapes, like circles and squares. You want to perform different actions on them, like calculateArea, calculatePerimeter or draw them on the screen.
Normally, you’d add this logic directly to the shapes themselves. But what if you later want to add a new action, like counting the number of sides? You’d have to modify all the shapes again.
The visitor pattern offers a better solution. The visitor pattern recommends we create a separate class called Visitor. This visitor acts like a specialist that can visit different shapes.
Each shape has a method to accept a visitor.
The visitor has different methods for each type of shape (Circle, Square, etc.). Now, when you want to perform an action on a shape, you create a visitor with that specific action and “visit” all the shapes with it. This way, you can add new actions (visitors) without changing the shapes themselves.
This pattern is useful when you want to separate algorithm for specific function from the object structure. For example, drawing is not the functionality of core shape. It’s polluting Shape object by adding how it should draw itself. So, it’s useful to clean up auxiliary behaviors. This pattern can be used when you expect lot of changes and new features being added down the line. This pattern can be useful when you want to perform an operation on all elements in a collection type structure. This pattern can also be used when a method makes sense only in some classes from a hierarchy, but not in others. In such situation, we can extract this behavior into a separate visitor class and implement only those visiting methods that accept objects of relevant classes and leave other methods empty.
How to implement Visitor Pattern?
The visitor pattern consists of following actors:
- Visitor: The
Visitorinterface declares a set of methods to visit concrete elements of an object as argument. Each concrete Visitor implements these methods for different concreteElementclasses. In our example, we haveAreaVisitorandPerimeterVisitoras concrete visitors. - Element: It is an interface which declares a method to
acceptvisitors. This method accepts a visitor using interface rather than concrete visitor type. Each concreteElementwill implement theacceptmethod. In example, we haveSquareandCircleasElementconcrete classes. - The Client may have a collection of elements. The client works with
Elementinterface rather than concrete type.
Practical Code - Shapes with different Functionalities
- First declare the interface for the
Visitorbased on whichElementtypes you want to support.
1public interface ShapeVisitor {
2 void visit(Square square);
3
4 void visit(Circle circle);
5}
- Define
Elementinterface with single method which accepts a visitor using its interface. Notice thatacceptmethod is using onlyShapeVisitorand no concreteShapeVisitor
1public interface Shape {
2 void accept(ShapeVisitor visitor);
3}
Define concrete
Elementtypes. In this case, define concreteShapesubclasses. Each subclasses implementacceptmethod forShapeVisitor.a. In this case, I have
Circle.1public class Circle implements Shape { 2 private double radius; 3 4 public Circle(double radius) { 5 this.radius = radius; 6 } 7 8 public double getRadius() { 9 return radius; 10 } 11 12 @Override 13 public void accept(ShapeVisitor visitor) { 14 visitor.visit(this); 15 } 16}b. Create another child class of
Shapedepending on how many shapes you want to support.1public class Square implements Shape { 2 private double side; 3 4 public Square(double side) { 5 this.side = side; 6 } 7 8 public double getSide() { 9 return side; 10 } 11 12 @Override 13 public void accept(ShapeVisitor visitor) { 14 visitor.visit(this); 15 } 16}Implement different
Visitorconcrete classes. In this case, I haveAreaVisitorto calculate area of a shape,PerimeterVisitorto calculate perimeter of a shape andDrawVisitorto draw any shape.a.
AreaVisitorhaving logic to calculate area of a given shape.1public class AreaVisitor implements ShapeVisitor { 2 @Override 3 public void visit(Square square) { 4 double area = square.getSide() * square.getSide(); 5 System.out.println("Area of Square: " + area); 6 } 7 8 @Override 9 public void visit(Circle circle) { 10 double area = Math.PI * circle.getRadius() * circle.getRadius(); 11 System.out.println("Area of Circle: " + area); 12 } 13}b.
PerimeterVisitorto calculate perimeter of a shape.1public class PerimeterVisitor implements ShapeVisitor { 2 @Override 3 public void visit(Square square) { 4 double perimeter = 4 * square.getSide(); 5 System.out.println("Perimeter of Square: " + perimeter); 6 } 7 8 @Override 9 public void visit(Circle circle) { 10 double perimeter = 2 * Math.PI * circle.getRadius(); 11 System.out.println("Perimeter of Circle: " + perimeter); 12 } 13}c.
DrawVisitorto draw a shape.1public class DrawVisitor implements ShapeVisitor { 2 @Override 3 public void visit(Square square) { 4 System.out.println("Drawing square of side: " + square.getSide()); 5 } 6 7 @Override 8 public void visit(Circle circle) { 9 System.out.println("Drawing cirlce with radius: " + circle.getRadius()); 10 } 11}Finally, the client can easily work with collection of shapes or even single
Shape.
1public class ClientMain {
2 public static void main(String[] args) {
3 Square square1 = new Square(5);
4
5 // Use AreaVisitor
6 ShapeVisitor areaVisitor = new AreaVisitor();
7 square1.accept(areaVisitor);
8
9 // Use DrawVisitor
10 ShapeVisitor drawVisitor = new DrawVisitor();
11 square1.accept(drawVisitor);
12
13 System.out.println("==================");
14 Square square2 = new Square(4);
15 Circle circle = new Circle(3.5);
16
17 List<Shape> shapes = Arrays.asList(square1, circle, square2);
18 for (Shape shape: shapes) {
19 shape.accept(drawVisitor);
20 }
21
22 }
23}
Advantages:
- Visitor pattern follows Open/Closed principle allowing easier extension.
- This pattern also makes code flexible using Single Responsibility principle. Each class single responsibility in this case.
Drawbacks:
- Each time a new
Elementis added or removed, you’ll have to modify all visitors. - The indirection of
ElementusingVisitorand vice a versa can be confusing to understand. - Each visitor may sometimes need access to private fields of
Elementwhich may require creating public methods for those fields and exposing them.
Comparison with Iterator Pattern
| Visitor Pattern | Iterator Pattern |
|---|---|
| This is designed using interfaces. | This is also interface based but may use anonymous class. |
| focuses on externalizing behavior | This encapsulates navigation but does not externalize it. |
| Useful when you have multiple visitors. | Usually single iterator in an application. |
Summary
- Visitor pattern is good for externalizing some behavior which does not make sense in the original
Elementclass. Thus promoting single responsibility. - It is useful when you are expecting changes in the application but can be sometimes difficult to know upfront.
- It can add little complexity in code.
- It can require changes in visitors whenever new element is added or removed.


Comments