Behavioral Design Pattern

Kotlin Design Patterns: Visitor Explained

Michal Ankiersztajn
ProAndroidDev
Published in
3 min readMay 6, 2024

--

Purpose of the pattern

Separation of an algorithm from the object structure. Think of a Visitor as someone who visits multiple places and does something different depending on the place. This means we need to modify Visitor instead of the place itself.

It’s mostly used to implement new things that don’t make sense inside an object but are required for the new feature to work.

What do we get from that?

  • Open/Closed: we can add new algorithms without changing the object structure.
  • Single Responsibility: each class is responsible for a different behavior.
  • Extension of a class with little to no modification to it.
  • Avoidance of polluting the object class.

The biggest downside is that you must update all Visitors when a class is added or removed from the hierarchy.

Implementation

Visitor Class Diagram

In the diagram, we have our Visitor that has methods for visiting concrete implementations of Element , therefore it’s dependent on each Element implementation.

At the same time, Element is dependent on a Visitor interface. We’re doing this to avoid checking the type of Element each time we want to use Visitor instead, we’ll accept Visitor and use the right method inside Element . The method will be a 1-liner that calls the right Visitor method.

Moreover, we’re able to create multiple ConcreteVisitors that can do different things.

Example

Your task is to add an Export to CSV feature to a body health system. Your manager is also thinking about adding other export options. Adding function to the Body would look at least strange it should be separated.

It’s a perfect use case for Visitor because we can add new export options easily without interfering with much of the body health system. Here’s how we’ll structure our code:

Body Health Export system

In real app BodyPart would also have other functions, but we want to keep things simple in the example. Let’s start by coding BodyParts along with Visitor interface:

interface Visitor {
fun visitEye(eye: Eye)
fun visitMouth(mouth: Mouth)
}

interface BodyPart {
fun accept(visitor: Visitor)
}

class Eye(val color: String) : BodyPart {
override fun accept(visitor: Visitor) {
visitor.visitEye(this)
}
}

class Mouth(val size: Int) : BodyPart {
override fun accept(visitor: Visitor) {
visitor.visitMouth(this)
}
}

Now, Visitor implementations have access to Mouth and Eye . Let’s start coding ExportToCSVVisitor :

class ExportToCSVVisitor : Visitor {
override fun visitEye(eye: Eye) {
println("csv eye: ${eye.color}")
}

override fun visitMouth(mouth: Mouth) {
println("csv mouth: ${mouth.size}")
}
}

Again, for simplicity Eye and Mouth have 1 parameter and we’ll just print the info.

Here’s how to use it:

fun main() {
val visitor: Visitor = ExportToCSVVisitor()
val bodyParts = listOf(
Eye("blue"),
Eye("borwn"),
Mouth(20),
)
bodyParts.forEach { it.accept(visitor) }
// csv eye: blue
// csv eye: borwn
// csv mouth: 20
}

There is no need to know the type of BodyPart when using Visitor methods because all of them have accept function that chooses the right Visitor function.

Thanks for reading! Please clap if you learned something, and follow me for more!

Learn more about design patterns:

Design Patterns In Kotlin

17 stories

Based on the book:

“Wzorce projektowe : elementy oprogramowania obiektowego wielokrotnego użytku” — Erich Gamma Autor; Janusz Jabłonowski (Translator); Grady Booch (Introduction author); Richard Helm (Author); Ralph Johnson (Author); John M Vlissides (Author)

--

--