Using the visitor pattern as a routing point (with Kotlin examples)

Luca Piccinelli
Aug 14 · 4 min read
Photo by Aaron Burden on Unsplash

Functional programming gained a big momentum in the IT field; many things come and go, but FP is not one of them. It is much more expressive than OOP.

I started digging into it a few years ago, during the LambdaConf in Bologna, and the more insight I get, the more I love FP.

In June, I went to an applied functional programming workshop and the teacher went deep into algebraic data types and pattern matching. I also finally understood what a monad is, but this is another story.

I used to think of pattern matching as an interesting way to destructure lists. Now, I know that it is a founding block of FP software design and an effective way to address the need for decoupling domain objects’ data and behaviors.

Let’s see an example!


ERP Localized by Country

Let’s say that we want to write ERP software, to compete in the markets of two different countries: Italy and Germany. We are going to write many CRUD operations in many domain objects. Nothing challenging here.

But, what about modeling country-specific business rules, in different data structures, inside the same functionality flow?

For example, both countries have articles in their databases and you definitely want to perform searches on those. But, search rules and fetched data can be totally different.

As the data structures are different, we can’t just have the domain entity Article — we need at least an ItalianArticle and a GermanArticle.

Consider that data stores could also differ by structure.


The FP Way

Let’s see a Scala implementation.

Write a sum type Article.

Then, we specialize it as the product types ItalianArticle and GermanArticle.

Also, we can expect that when we search for something, sometimes we find nothing. So, we are going to also consider ArticleNotFound.

Every time we receive an Article back from somewhere, we are going to pattern match on it. Inside the match, we gain access to its specialized data.


Isn’t It a Switch?

At first sight, it could look like a classic procedural “switch”, followed by a down-casting.

There is an important difference: the compiler is aware of whether we are matching every type of Article or not. If, for example, we forget to match ArticleNotFound:

Then, by default, the sbt compiler is going to raise a warning.

sbt compile output
sbt compile output
Figure 1: sbt compile output

As the compiler knows that something is wrong, we can make it raise an error instead of a warning.


New Requirements: Spain

At this point, something totally unexpected happens! The business comes to us with a new requirement: we also need to distribute in Spain.

Actually, we were prepared, so let’s add EspArticle to our Article sum type.

Now, we need to add Spanish business logic.

With a normal switch, it would be a pain to search for every place where we implemented country-specific business rules.

Instead, with pattern matching, the compiler tells us where we have to intervene.


Is It Possible With OOP?

Yes of course, with the visitor pattern!

If you don’t know about the visitor pattern, have a look at this Wikipedia page. If you don’t know about the Gang of Four (who wrote the book Design Patterns: Elements of Reusable Object-Oriented Software), have a read of it.

The visitor pattern has been known for a long time as an anti-pattern. When you add a new item type, you are going to add a new method to the visitor interface.

In this case, the compiler breaks every concrete visitor, until you implement the new method in each of them. This is one of the reasons for an anti-pattern.

Actually, for our concerns, this “issue” looks exactly like what we are searching for!


Kotlin Example

Here is a Kotlin implementation of our sum types:

You’ll notice that, instead of using the names “accept” and “Visitor”, I prefer to think about “data that are applied to a data consumer”. (I’m looking for better names, so if you have suggestions, let me know.)

And, here, we can implement country-specific business rules.

The semantic of this example is totally the same as FP pattern matching.

When we add the new country, the compiler is going to break everything, until we don’t implement the new fun use(article: SpaArticle) in every ArticleConsumer.


Conclusion

The original purpose of the visitor pattern was to iterate an operation over collections of heterogeneous objects, which don’t share the same interface and data types.

In this article, I proposed to use it, instead, as a routing point. It is useful when you end up enumerating your domain entities and need to set a localized domain context.

Enumerating domain entities in methods is argued as being an issue. In this use case, it is the key feature of the pattern.

I also demonstrated that this usage is semantically equal to FP pattern matching.

Thank you for reading!

Better Programming

Advice for programmers.

Thanks to Zack Shapiro

Luca Piccinelli

Written by

I’m a programmer. I love programming, any language, any paradigm

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade