Off-label use of the visitor pattern

In this article, I’m going to quickly go over a modified visitor pattern that I use for custom mapping when I’m not off abusing AutoMapper.  I’m not going to explain the visitor pattern here (maybe I will in a different blog, but today ain’t the day), but rather going over a slight bastardization of the pattern.

So let’s get one thing straight here before we start — patterns are solutions, not some sort of coder dogma. My point is, that the GoF patterns are time tested and true, but sometimes you just gotta… dance. Typically, when I see someone try to explain the visitor pattern, it’s either a total disaster of an explanation, I start to bleed from my eyes and ears, or … it’s a reporting example. In short, the reporting example basically sends in a visitor and that visitor traverses an object graph and collects a bunch of data. At the end, the visitor will have been updated with the final result of it’s trip into the bowels of it’s host object. Boring.

On of the other downsides to the textbook pattern is that any time you updated the visitor to handle a different object, you need to update the interface… and if you update the interface, then you gotta update every implementation of that interface. Boooooooo! This doesn’t aim to solve any of that; I’m just complaining. But, this thing outputs an object instead of simply collecting, and it doesn’t have that pesky interface problem. Alright, let’s get to it!

So for a while at my job, some of the leads were highly against AutoMapper for whatever reason, so we opted into doing all mapping by hand. Unfortunately, everything grew so fast that no standards were set in place and we ended up with, I shit you not, like 4 different mapping patterns, almost none of which were re-usable. To try and make an attempt at cleaning things up, I implemented this doozy.

So this base class takes care of most of the magic. All you have to do is implement it:

So here’s what’s happening:

  1. Our convention is that any implementation of Mapper will take one or more different types of objects and output an instance of T.
  2. In the override RegisterMappers, we tell the class what types of objects we’re prepared to map. In this example, we’re saying that we’re going to support mapping instances of Input1 and Input2 to an instance of Output1
  3. The parameters of Register(Type, Func) are
    1. The type of the input that we’re going to be ready to map
    2. The internal method that will contain the mapping logic for that particular input type along with a cast to that parameter.
    3. So for instance, the first Register() call is saying that for types of Input1, we’re going to call the method that takes Input1. The compiler will know which one to use based on that cast (e.g. input as Input1)

How do we use it?

What I’m doing here is adding an extension to the System.Object type that will allow me to apply a mapper to any instance of any object. Now to use the mapper, I simply need to call that extension with an instance of our mapper implementations. Here’s an example:

Now here’s the neat thing. In true visitor fashion, you can reuse and chain these mappers together by using them inside of your mapper logic…

Now, as objects more and more complex and the composition gets deeper and deeper, this pattern really starts to save you time. You can reuse the mapper visitor, or invoke new ones at any point in the graph and polymorphism will take care of the rest.

This may seem kinda nuts but it’s really not. By creating mappers for all of your known objects, all you have to do in order add mapping logic for a new source object is to drop it in the existing mapper and register it. Or if you need to update the mapping logic, you only have to do it in one place. Over time, this becomes quite the time saver.

There are plenty of other solutions to this problem out there, but I think this one might help some of the anal retentive devs out there that want to make sure they have explicit control over everything that happens during a transformation like this. Hopefully, this will help someone out there!

Leave a Reply