dateo. Coding Blog

Coding, Tech and Developers Blog

clean architecture
visitor pattern
design patterns

The visitor pattern in C# — The Good, The Bad, And The Ugly

Dennis Frühauff on July 30th, 2021

The visitor pattern is probably among our industry's most unused software design patterns. While there are quite a few examples and tutorials on the internet, I am personally lacking the ones where one can actually see the benefit of using this pattern over others. Because, to me, there are good reasons for us software engineers to make use of the visitor pattern, especially when you are working which feature-rich applications.

So let’s build up a scenario that we can try to tackle:

Imagine you are building an app or a desktop application, that is expected to fulfill simplified requirements like these:

The user can draw something on a canvas, e.g., a sketch.
The user can add text to the canvas.
The user can change basic properties of the canvas, e.g., the background color.

Now, you can easily image something like Photoshop, GIMP, or even MS Paint to be such an app, choose what ever you like.

Being a usability enthusiast, you think about handling these user “actions” in such a way that they do not interact with each other. For example, you might want to hide the properties dialog once the user hits the draw button. Or you would detach the pencil from the pointer once the user wants to enter some text. There are of course different approaches to these problems and I would like to present three of these, including the visitor pattern, which is my personal favorite in this case.

The Bad

Let’s get over with the bad solution first: Event-based approach. Once the user hits, e.g., the draw button, you could raise an event that will tell all other actions, like TextAction and PropertiesAction, to react to it. The handlers will then probably abort text input, or close the options window. It is likely that the DrawAction itself will receive its own event, so it might even be necessary to filter for the initial sender of the event.

Also, all actions need to know each other, which is a clear no-go in terms of extendability (see the “O” of S.O.L.I.D.). Sound pretty nasty, right? Please do not do this…

The Ugly

Naturally, there is a better solution to this event-based approach. Injecting all these actions into one another is hell, therefore you would probably solve this issue by an object that knows which state the application is in — a state machine. Here, when the user hits the draw button, the DrawAction will tell the state machine to change to state “Draw” and everyone else can react to the change of the state machine. In its simplest form, this could look something like this:

public enum ApplicationState  

public class NewStateEventArgs : EventArgs  
    public NewStateEventArgs(ApplicationState newState)  
        NewState = newState;  

    public ApplicationState NewState { get; }  

public class ApplicationStateMachine  
    public ApplicationState CurrentState { get; private set; } = ApplicationState.None;  

    public event EventHandler<NewStateEventArgs> NewApplicationState;  

    public void ChangeState(ApplicationState newState)  
        CurrentState = newState;  
        NewApplicationState?.Invoke(this, new NewStateEventArgs(newState));  

This still is an event-based approach — however, the event itself is now dispatched to a different object. Each action in the application will of course have to know about this object and can handle the results differently.

Going back to S.O.L.I.D.: Still, extending your application is a lot work. Everytime a new action has to be added, the enumeration needs to be extended, may even the state machine needs to be set up differently. (And yes, you could do this with strings to avoid these issues, but, hey, readability?)

The Good

Now, let’s do it the right way. Having an object that knows about the current state was a good idea already, but having all these actions react to the change of this object is not. Instead, this one object should be able to alter the actions! Consider the following UML:

Screenshot 1

Example of a visitor implementation. Every IAction can be visited by the IActionHandler.

First, the central object here is the ActionHandler (our visitor), which will be injected via its interface IActionHandler into every action that we have. Second, each action will be forced to have a set of methods by IAction. Third, every IAction will be registered in the ActionHandler.

Now, if our imaginary user hits the draw button, will only call IActionHandler.ExecuteAction(“DrawAction”). Our ActionHandler implementation could like something like this:

public class ActionHandler  
    private readonly Dictionary<string, IAction> actions = new Dictionary<string, IAction>();  

    public void RegisterAction(IAction actionToRegister)  
        this.actions.Add(actionToRegister.Name, actionToRegister);  

    public void ExecuteAction(string name)  
        foreach (var action in this.actions.Values)  

        var actionToExecute = this.actions[name];  

        foreach (var action in this.actions.Values)  

This is of course very simplified, without any fail-safe error handling, but you get the gist, hopefully. The ActionHandler cancels all actions that it knows of, hides them all and then executes the one that requested its execution, that is, it is visiting all IAction implementation and performing operations on them.

Once that is done, every action is requested to return back to its default state. Voilà, basic visitor pattern successfully implemented.

Let’s wrap that up quickly — what do we gain when implementing the visitor pattern here:

  • Easy-to-read, easy-to-follow code.
  • Not a single event was needed for the implementation, making the debuggers life very easy.
  • We have a single instance in an application, controlling the overall behavior.
  • If a new action comes up, say, PrintAction(), no need to change existing code. Just implement the action and register it in the ActionHandler.

I hope that you could get an idea about this pattern and its real-world use. I will be happy to read your comments on this!

Please share on social media, stay in touch via the contact form, and subscribe to our post newsletter!

Be the first to know when a new post was released

We don’t spam!
Read our Privacy Policy for more info.

We use cookies on our website to give you the most relevant experience by remembering your preferences and repeat visits. By clicking “Accept All”, you consent to the use of ALL the cookies. However, you may visit "Cookie Settings" to provide a controlled consent.