Introduction to the Visitor Pattern

One of the goals we should always keep in the back of our minds when crafting code is long term maintainability.  As code changes it generally degrades.  Inadvertent dependencies are created, the original intent is sometimes overlooked, and we start to see hacks around limitations of existing surface areas unless we have a security guard on watch night and day.  When long-term maintainability is no longer thought as of being important or if this focus moves elsewhere in our application or to other applications altogether, changes will be made without as much caution and will most likely start degrading the code.  If we could accurately forecast changes and make accurately forecasted growth points in our application live in configuration files or data storage of some kind, the world would be a much better place and our code would live forever and never have to change.  Of course, no one has a crystal ball and this is not always realistic, possible or the cost to come close to this ideal it is just too high (kind of like 100% unit test code coverage).  The fallback position to take is that we would want all changes to our code to only be additive.  This means that any new change would only require adding new methods or classes and not modifying existing code which reduces the likelihood of "breaking" changes getting in the code base.  Every time we modify existing code we take the risk of cascading breaks or unintended side-effects so logically modifying existing code (especially inside of methods) should be the last resort and avoided if at all possible (if only for our own sanity).  Achieving our fallback plan (additive code only) and keeping the original intent of our code alive and as clean as possible is made easier with the visitor pattern.

To demonstrate, let's say we have a library that is going to help provide a structure for modeling animal behavior.  The trick is going to be to keep our Animal base class clean and not have it get bogged down with a lot of implementation details for specific animals because each animal can all behave very differently.  If we were to stick all our functionality in each manifestation of an animal, or worse yet have an AnimalType enum and litter ourr code with switch statements, the software could quickly degrade and become un-maintainable.  Also any functional changes to an Animal would then have to be implemented in the Animal and we step on our goal of having code changes be additive.  However, we do need to acknowledge that animals need to be able to perform different functions.  Of course this becomes more complicated because there are so many different types of animals. 

Enter the "Visitor" pattern.  We will have a single method for the purpose of extensibility on our one animal base class.  It will take a visitor object which then performs functionality specific to an animal.  And so here we have the visitor pattern in its simplest manifestation.

Another way to think of the visitor pattern is a conceptual cross between a command pattern and the strategy pattern.  The command for the Animal to execute is passed and then executed by the Animal in a similar way that an implementation of the strategy pattern would set a longer term strategy for a class.  The main difference is that the Animal class does not keep its reference to the visitor like it would have with a strategy.  The difference between how a visitor class is executed compared to a command pattern really shows the power (and beauty) of the visitor class and I'll get into that a bit later in the article.

public abstract class Animal
{
    protected Animal(string name)
    {
        m_Name = name;
    }
 
    private string m_Name;
    public string Name
    {
        get { return m_Name; }
    }
 
    public virtual void Accept(AnimalVisitor visitor)
    {
        visitor.Visit(this);
    }
}
 
public abstract class AnimalVisitor
{
    public abstract void Visit(Animal animal);
}

Now for some concrete Animals (and not the lawn ornament kind).

To demonstrate how extensible the visitor pattern makes our base Animal class, we'll create a whole new code library for the concrete animals.  This ensures our Animal core framework does not get corrupted because we will impose a harsh boundary between the framework code and the implementation code (they are in separate libraries).  Even though this can be a draconian step, it can be worth it if we have a large team with both senior and junior devs.  The senior devs can develop and maintain the framework and the junior devs can work on the consumption of that framework.  This helps to keep the core framework pure and clean and allows the senior devs to always remain upstream of the junior devs.  But I digress.....

Following are two concrete animals; a Giraffe and an Alligator.  They contain no implementation just for the sake of simplicity.

public class Giraffe : Animal
{
    public Giraffe(string name) : base(name) { }
}
 
public class Alligator : Animal
{
    public Alligator(string name): base(name) { }
}

And we build a visitor to give our animals the ability to run...


public
class RunVisitor : AnimalVisitor
{
    public override void Visit(Animal animal)
    {
        Console.WriteLine(animal.Name + " is now running");
    }
}

And when we run our code:

RunVisitor target = new RunVisitor();
Alligator alfred = new Alligator("Alfred");
Giraffe jerry = new Giraffe("Jerry");
 
target.Visit(alfred);
target.Visit(jerry);

We get the following output:

Alfred is now running
Jerry is now running

And now we have completed implementation of our visitor pattern.  Pretty exciting, huh?

I know... not really. It's about as exciting as concrete lawn ornaments.   Well... even though I imagine this isn't anything mind blowing for you yet, if you are not familiar with the template pattern, I promise the next part will be.

Ok, let's say we get a new requirement.  We now have to model fish.  Luckily since we had enough foresight to realize the code would grow in this direction and used the visitor pattern, we are all set for the change.  Our change will consist of an additional class with a small additive change in our visitor class. So first we add our fish class...

public class Fish : Animal
{
    public Fish(String name) : base(name) { }
}

Then we have to update the visitor because fish are generally poor runners.

public class RunVisitor : AnimalVisitor
{
    public override void Visit(Animal animal)
    {
        Console.WriteLine(animal.Name + " is now running");
    }
 
    public void Visit(Fish animal)
    {
        Console.WriteLine("fish are generally poor runners");
    }
}

And we now execute our code....

RunVisitor target = new RunVisitor();
Alligator alfred = new Alligator("Alfred");
Giraffe jerry = new Giraffe("Jerry");
Fish fredrick = new Fish("Fredrick");
 
target.Visit(alfred);
target.Visit(jerry);
target.Visit(fredrick);

And get the following output...

Alfred is now running
Jerry is now running
fish are generally poor runners

Did you catch that? This is the magic behind the template pattern.

Now what just happened? 

We were able to catch a more specific instance of animal in our RunVisitor class and provide a completely different implementation! 

This is part of the power of the visitor pattern, we can code to base class implementations and the visitor can "un-abstract" in order to run type-specific  code without having to explicitly check the type in an "if" or "switch" statement (which is really a very poor programming practice anyways). 

How do you like them apples?

Let's look at another example that will help demonstrate the extensibility of the pattern: Let's say you want to add functionality for a food chain. All we'll have to do is create another visitor class.  Note that the change to our code is only additive (helping to prevent cascading breaks and unintended defects).

public class EatOtherAnimalVisitor : AnimalVisitor
{
    public EatOtherAnimalVisitor(Animal snack)
    {
        m_Snack = snack;
    }
 
    private readonly Animal m_Snack;
    public Animal Snack { get { return m_Snack; } }
 
 
    public override void Visit(Animal animal)
    {
        throw new NotSupportedException("Edibility is unknown");
    }
 
    public void Visit(Alligator animal)
    {
        Console.WriteLine(String.Format("Alligator ate {0} the {1}", m_Snack.Name, m_Snack.GetType()));
    }
 
    public void Visit(Giraffe animal)
    {
        Console.WriteLine(String.Format("{0} is a vegetarian and refuses to eat {1} the {2}", animal.Name, m_Snack.Name, m_Snack.GetType()));
    }
}

And all we have to do to consume our new functionality is the following:

Alligator alfred = new Alligator("Alfred");
Giraffe jerry = new Giraffe("Jerry");
Fish fredrick = new Fish("Fredrick");
 
var eatFredreck = new EatOtherAnimalVisitor(fredrick);
 
eatFredreck.Visit(jerry);
eatFredreck.Visit(alfred);

Which when run, gives us the following result:

Jerry is a vegetarian and refuses to eat Fredrick the VisitorSample.Extended.Fish
Alligator ate Fredrick the VisitorSample.Extended.Fish

I hope this article helped to demonstrate where and how you might find the visitor pattern useful.  If you get nothing else out of this article, it should be that the template pattern can provide behavior extensibility and can also figure out the type of an object and execute appropriate functionality for that type when all we have is a reference to the base class.

Until next time,

Happy coding

Up Next
    Ebook Download
    View all
    Learn
    View all