What is the visitor design pattern? (Java)

Mingle Li
6 min readMar 4, 2022

The concept of visitors solves a specific problem. It allows you to apply a function to UNION data, and it allows you to have more flexible object-oriented code.

THE PROBLEM:

You have some sort of union data (e.g. Shapes). It can be “one of” many values.

IShape is one of: Circle, Square

NOTE: I made Square be Rect initially but changed to Square to make it simpler but that made xSide and ySide redundant. Let’s just stick with it :)

Let us try to get the area of each shape.

THE PREVIOUS WAY (DO NOT DO THIS!):

Let’s just have a general getArea() method to return the area! No problem.

getArea() implemented as an interface method across children classes

What’s the problem here?

Now let me ask you to get the perimeter of a shape. You have to add:

  1. A new method to the interface
  2. A new method to each class that implements the interface, so
  • A method for Circle
  • A method for Square

On a small scale, this doesn’t seem bad.

What if you had an interface with 10 classes implementing it? What if your interface needed to have 50 methods? You can imagine how many methods you’d need to implement. Your code will be increasingly complex because you have no choice but to lump all your logic into one big interface and one big file.

THE SOLUTION:

Keep the interface and all its subclasses stupid simple. Export all the logic to smaller, understandable classes.

This is where the visitor design pattern comes in! But you still have the question: “What is a visitor?????”

A VISITOR IS A FUNCTION.

Like a math function. It takes an X and returns a Y. It takes in a T and returns an R. It takes in an INPUT PARAMETER and returns an OUTPUT PARAMETER. That’s all it is.

Let’s return to the original task: Let’s get the area of each shape. (I reset IShape, Circle, and Square to have no methods).

However, let’s think about it in a different way.

We have a Shape. Let’s create a function that takes in a Shape and returns the area

Function: Shape → area

If we can apply that function onto the Shape, it would look something like:

Function.apply(Shape) → area

Let’s start by creating an IShapeVisitor (which is a function!!!) as a general function template for anything to do with Shapes.

IShapeVisitor: IShape -> whatever you’d like (so generic type, let’s name it T)

IShapeVisitor is a Function that takes in an IShape and returns a T, whatever T may be. Its purpose is to “translate” IShapes into Ts.

Remember: our goal here is to create a function that will get us the area of a shape. Let’s create a ShapeToArea visitor (aka a function!) that takes in an IShape (already covered by IShapeVisitor) and returns a Double (for the area).

ShapeToArea is a Visitor/Function that translates an IShape to a Double.

Eclipse is angry because we need to implement some method. Why? The hierarchy of classes goes:

IFunc → IShapeVisitor → ShapeToArea

And we have a method, apply() from IFunc, that we haven’t implemented yet! Let’s do that.

We need to implement apply() inherited from IFunc, but what do we put?

Okay, we got the function ShapeToArea. We have the input IShape. How do we get the area?

THE PROBLEM:

Because Shape is a union data type, it can either be a Circle or a Square. We can’t put a general getArea() and do dynamic dispatch, because that’s the opposite of what we’re trying to do. Remember! Our major goal is to export the logic of getting the area to outside of the IShape and into the ShapeToArea function/visitor/class.

  1. We’re starting in the ShapeToArea. Let’s visit the IShape and ask, “Hey, I don’t know what shape you are. What shape am I visiting right now?”
  2. The IShape will accept() the visitor and reply, “Hi! You are currently visiting a Circle/Square. Here’s all my data!
  3. The ShapeToArea will reply, “Thanks! I’ll do what I need to do with the data you gave me.”

Sounds like you’re going back and forth, right?

Starting from ShapeToArea → Go to IShape → Come back to ShapeToArea

This sounds like a job for…

THE SOLUTION:

Double Dispatch!

1. In order for the ShapeToArea to go over and visit the IShape, the IShape first needs to accept() the visitor. The visitor is here on the task to turn IShape into Double (aka type T). Thus, the IShape needs to assist the visitor on its task and help it return a Double (aka type T).

The shape must accept the visitor; this is the receiving end of the function saying “Give me the function that you’re gonna apply onto me!” Return T, or the return value of the Visitor function.

2. Once the IShape has accepted the visitor, it needs to tell the visitor, “Hi! You are currently visiting a Circle/Square. Here’s all my data!

Dispatching to different methods — this is double dispatch at work.

3. We haven’t yet created the methods for when a visitor knows what it’s visiting!

All the logic goes into the Visitor class.

Using this double dispatch, we now figured out what data type we’re actually visiting. Now, it’s a matter of writing the logic in this exported, outside ShapeToArea function.

And with the apply() function where we started with an IShape as the input, all we have to do is visit it and let the IShape accept us, the visitor.

This tells the shape, “Hey, I’m gonna apply my function onto you. First, I need to know what type of Shape you are!”

And we’re done!

Why is this “so much better”?

Well, let’s write a test first to see how it works in action.

Instead of doing something like this.circle.getArea(), it’s now new ShapeToArea().apply(this.circle). We are applying a function (ShapeToArea: IShape -> Double) onto an IShape.

Let’s gooooooooooooooooooooooooooooooooo

Now, I present to you a new task:

Task: Get the perimeter of a shape.

PREVIOUSLY (Interface Methods)

Have to add:

  1. getPerimeter() to the interface
  2. A new method to each class that implements the interface, so
  • getPerimeter() for Circle
  • getPerimeter() for Square

NOW (Visitors)

Have to add:

  1. A new visitor, ShapeToPerimeter
  2. ……..that’s it!
Make a new Visitor class to do the heavy lifting
And it works!

To add that new functionality and logic, we didn’t even touch the IShape class.

Now that’s awesome.

Mingle Li | li.mingle@northeastern.edu

Written 3/1/2022 to explain visitors in Fundies 2

--

--