Visitor Design Pattern

Design Pattern Tutorial Series

Visitor Design Pattern in Java - 5-Step Guide

The Visitor Pattern allows you to add new methods to other classes without creating major changes. Additionally, these methods can be customized to match the desired functionality for the object for which it is added to.

In the simplest form of OOP (Object-Oriented Programming), you might assume that if you wanted to add customized methods to a class, that extension (extends) would be the way forward.

However, as we have seen from many other design pattern (e.g. strategy and decorator) and we will see now, inheritance is rarely an optimal code structure unless you are using it probably.

Table of Contents

INTEGU - visitor-design-pattern-overview-

Step 1 - The Objective

The objective of this tutorial is to create a math-cheating-system. We have been hired by a student to write a program which will be able to calculate the area and cost of a given geometrical shape (circle, square, or triangle). Perhaps I am stretching the topic of the objective this?

As we know, each of the geometrical shapes have a different formula for calculating the area. Likewise, the also have different prices per unit of area.

The student’s only requirement is that the system should be able to change between calculating the area or the price while the program is running (runtime).

With the objective stated, let’s see how we would be able to solve it without the visitor pattern.

INTEGU - visitor-pattern-objective

Step 2 - Class Inheritance

The first and most simple approach would be to use the common OOP strategy of class inheritance.

Given that the code is based around the three main geometrical-shape-classes of Circle, Square and Triangle, we will be creating two extending classes for each shape class, which provides the class with the functionality of either calculating the area or cost of the shape.

This means that we for each of our shape class will be having three different variations (e.g. Square, SquareAreaCal, and SquareCostCal). An example of the code can be seen below.

Even though this solves the main objective of creating a math-cheating-system, it does not fulfill the student’s requirement of being able to change the shape-classes calculating method at runtime. At the same time this solution also introduces other problems.

public class Main {
        SquareAreaCal squareAreaCal = new SquareAreaCal(5, 10);
        double area = squareAreaCal.getArea();
        System.out.println("Area of triangle: " + area);
        SquareCostCal squareCostCal = new SquareCostCal(5, 10);
        double cost = squareCostCal.getCost();
        System.out.println("Cost of triangle: " + cost);
        /** Continue the same way for the triangle- and circle object*/
    }
INTEGU - visitor-pattern-disadvantage

It must be determined at the point of initialization (compile time) which class we want to use. It would be preferable to remain in control of the object at runtime, so that we with a single line of code can determine if the object should have the functionality.

We cannot create common code structures for all objects, since the added functionalities are first being added when we use the extended class. This will quickly make the main context into spaghetti code and require a growing number of if-statements to ensure that we are working with the correct instance of an object.

Finally, whenever we use inheritance to alter classes, we will quickly experience a class explosion. This happens because each of the base class must be extended by a new class for each of the added functionalities. This means that for each new functionality or base object an equal amount to the number of opposite functions/classes needs to be created (aka. exponentially growing).

Having determined all the shortcomings of using class inheritance, let’s try and see how this issue could be solved by utilizing the visitor design pattern.

Step 3 - Concept Of The Visitor Pattern

The core concept of the visitor design pattern is based on two interfaces: the visitor– and visitable interface.

The visitor interface declares one method, the visit method, which is then overloaded for each type of object that it has to add functionality to. In our case that would be the Circle-, Triangle-, and Square objects.

Whichever additional functionality we want to add to the shape objects, then must implement the interface and in the implementation of the overloaded method, and state how the functionality should work in that specific scenario.

The visitable interface only declares one method, the accept method, which takes in a visitor object as its only argument.

All shape objects have to implement the visitable interface and override the interface method. In the method, we use the visitor argument to call the visitor method and provide the shape object (this) as the argument.

The advantages of the visitor design pattern is similar to any other design pattern which utilize composition over inheritance, no class explosions, because we do not have to create a class for each variation of algorithm and shape object, and flexibility at runtime, since the association between algorithm and shape object have not been created in the code. Except for the interfaces, which enables the decoupled code structure.

 

INTEGU - visitor-pattern-advantages

Step 4 - Class Diagram (UML)

As we have learned from the previous section, the visitor pattern is nothing more then two interfaces, visitor and visitable, implemented appropriatly by respectivily the concrete algorithms classes (e.g. CostCalculator) and the element classes (e.g. Square).

 

This therefore makes the class diagram straight forward as the only link between the two interfaces and their implementations is the context for which they are used in (e.g. main method).

INTEGU - visitor-design-pattern-class-diagram-INTEGU

Step 5 - Code (Java)

Given that code is sometimes difficult to learn through concepts and theory, I provide the code for the example.

 

Hopefully, this will be sufficient for you to learn to identify and implement the visitor pattern in your future endeavors as a software developer.

Visitor

public interface Visitor {
    double visit(Triangle triangle);
    double visit(Circle circle);
    double visit(Square square);
}
public class CostCalculator implements Visitor {
    private static final int COST_PER_SQAURE_UNIT = 20;
    private static final int COST_PER_TRIANGLE_UNIT = 30;
    private static final int COST_PER_CIRCLE_UNIT = 10;
    @Override
    public double visit(Square square) {
        int area = square.getHeight() * square.getWidth();
        return area * COST_PER_SQAURE_UNIT;
    }
    @Override
    public double visit(Triangle triangle) {
        int area = (triangle.getHeight() * triangle.getWidth()) / 2;
        return area * COST_PER_TRIANGLE_UNIT;
    }
    @Override
    public double visit(Circle circle) {
        double area = circle.getDiameter() * 3.14;
        return area * COST_PER_CIRCLE_UNIT;
    }
}

Visitable

public interface Visitable {
    double accept(Visitor visitor);
}
public class Square implements Visitable {
    private int height;
    private int width;
    public Square(int height, int width) {
        this.height = height;
        this.width = width;
    }
    public int getHeight() {
        return height;
    }
    public int getWidth() {
        return width;
    }
    @Override
    public double accept(Visitor visitor) {
        return visitor.visit(this);
    }
}

How to use the Visitor Pattern

public class Main {
    public static void main(String[] args) {
        Square square = new Square(5, 10);
        printShapeArea(square);
        printShapeCost(square);
        Triangle triangle = new Triangle(5, 10);
        printShapeArea(triangle);
        printShapeCost(triangle);
        Circle circle = new Circle(10);
        printShapeArea(circle);
        printShapeCost(circle);
    }
    private static void printShapeArea(Visitable shape) {
        AreaCalculator areaCalculator = new AreaCalculator();
        double area = shape.accept(areaCalculator);
        System.out.println("Area of triangle: " + area);
    }
    private static void printShapeCost(Visitable shape) {
        CostCalculator costCalculator = new CostCalculator();
        double cost = shape.accept(costCalculator);
        System.out.println("Cost of triangle: " + cost);
    }
}

 

Recommended Reading

Article

  • Theoretical walkthrough – Source Making – Blog
  • Practical walkthrough – Tutorialspoint – Blog
  • Practical walkthrough – Springframework Guru – Blog

Tools

    • Camtasia – Used for illustrations – Camtasia

Video Tutorial 

  • Practical – Derek Banas – YouTube

Books

About

Hi, I'm the Author

My name is Daniel H. Jacobsen and I’m a dedicated and highly motivated software developer with a masters engineering degree within the field of ICT. 

I have through many years of constantly learning and adapting to new challenges, gained a well-rounded understanding of what it takes to stay up to date with new technologies, tools and utilities. 

The purpose of this blog is to share both my learnings and knowledge with other likeminded developers as well as illustrating how these topics can be taught in a different and alternative manner.

If you like the idea of that, I would encourage you to sign up for the newsletter.

Cheers! 🍺

Didn't Find What You Were Looking For?

Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages
Scroll to Top