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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
// this is the resuable base class public interface IMapper { T Map(object input); } public abstract class Mapper : IMapper { private Dictionary<Type, Func<object, T>> _mappers; public Dictionary<Type, Func<object, T>> Mappers { get { if(_mappers != null) return _mappers; _mappers = new Dictionary<Type, Func<object, T>>(); RegisterMappers(); return _mappers; } } public abstract void RegisterMappers(); public virtual T Map(object input) { // throws if not registered return Mappers[input.GetType()](input); } public void Register(Type input, Func<object, T> action) { Mappers.Add(input, action); } } |
So this base class takes care of most of the magic. All you have to do is implement it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
// these are sample implementations of the visitor public class Output1Mapper : Mapper // the mapper generic parameter is the type you want to output { public override void RegisterMappers() { // register by specifying your input type, and the function // that will run for that type Register(typeof(Input1), input => Map(input as Input1)); Register(typeof(Input2), input => Map(input as Input2)); } // the method that will run for Input1 public Output1 Map(Input1 input) { // in a real world example, we would use our input instance to instantiate // and hydrate an instance of our Output1 class. For this simple example, // we are just setting a single property via its constructor return new Output1(input.Input1Name); } // the method that will run for Input2 public Output1 Map(Input2 input) { return new Output1(input.Input2Name); } } |
So here’s what’s happening:
- Our convention is that any implementation of Mapper will take one or more different types of objects and output an instance of T.
- 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
- The parameters of Register(Type, Func) are
- The type of the input that we’re going to be ready to map
- The internal method that will contain the mapping logic for that particular input type along with a cast to that parameter.
- 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?
|
// by adding this to your system namespace, all objects will be able to be // remapped using an instance of a mapper public static class Extensions { public static T Map(this object obj, IMapper mapper) { return mapper.Map(obj); } } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
void Main(string[] args) { var i1 = new Input1(); var i2 = new Input2(); IMapper mapper1 = new Output1Mapper(); IMapper mapper2 = new Output2Mapper(); // run the first mapper against our objects var mapped1 = i1.Map(mapper1); var mapped2 = i2.Map(mapper1); // run the second mapper against the same objects var mapped3 = i1.Map(mapper2); var mapped4 = i2.Map(mapper2); Console.WriteLine(mapped1.Message); // Mapped Input1 to Output1 Console.WriteLine(mapped2.Message); // Mapped Input2 to Output1 Console.WriteLine(mapped3.Message); // Mapped Input1 to Output2 Console.WriteLine(mapped4.Message); // Mapped Input2 to Output2 } |
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…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class Output1Mapper : Mapper // the mapper generic parameter is the type you want to output { public override void RegisterMappers() { // register by specifying your input type, and the function // that will run for that type Register(typeof(Input1), input => Map(input as Input1)); Register(typeof(Input2), input => Map(input as Input2)); } // the method that will run for Input1 public Output1 Map(Input1 input) { // in a real world example, we would use our input instance to instantiate // and hydrate an instance of our Output1 class. For this simple example, // we are just setting a single property via its constructor return new Output1 { Prop1 = input.InnerProp.Map(new Output2Mapper()), Prop2 = input.Name, Prop3 = input.InnerProp2.Map(new Output3Mapper()) }; } // the method that will run for Input2 public Output1 Map(Input2 input) { return new Output1(input.Input2Name); } } |
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!