Visitor Pattern in C#

Visitor Pattern in C#

The Visitor is a behavioural design pattern that lets us separate algorithms from the entities on which they operate.

The Visitor pattern lets us operate on objects by representing that operation as an object unto itself. We can then operate on said objects without changing the definitions or functionality of those objects.

You can find the example code of this post, on GitHub.

Conceptualizing the Problem

Imagine that we are working on an application with geographical information structured as one humongous graph. The graph has different kinds of nodes. Some represent complex entities such as cities, and more granular things like shops and businesses, sightseeing areas, etc. The nodes are connected if there's a road between the real-life objects they represent. Under the hood, each node type is represented by its class.

No alt text provided for this image

Now we need to implement a mechanism to export the graph into XML format. At first, the job seemed pretty straightforward. We can add an export method to each node class and then leverage recursion to go over each node of the graph, executing the export method. The solution is straightforward and elegant: thanks to polymorphism, we aren't coupling the code called the export method to concrete classes of nodes.

Unfortunately, the system architect refused to allow us to alter existing node classes. The code is already in production, and a potential bug in the implementation can crash the application. 

No alt text provided for this image

Besides, it doesn't make sense to have the XML export code within the node classes. The primary job of these classes was to work with geodata. The XML export behaviour would look alien there.

There's another reason for the refusal. It is highly likely that after the implementation of this feature, Mr Smithers from the marketing department would ask us to provide the functionality to export into a different format, or request some other weird stuff. This would force us to change those precious classes again.

The Visitor pattern suggests that we place the new behaviour into a separate class called visitor, instead of trying to integrate it into existing classes. The original object that had to perform the behaviour is now passed to one of the visitor's methods as an argument., providing the method access to all necessary data contained within the object.

In our case, the actual implementation will probably be a little different across various node classes. Thus, the visitor class may define not one, but a set of methods, each of which could take arguments of different types:

No alt text provided for this image

The problem now is how to deal with the whole graph. These methods have different signatures, so we can't use polymorphism. To pick a proper method that's able to process a given object, we'd need to check its class.

No alt text provided for this image

Why don't we use method overloading? We can give all methods the same name, even if they support different sets of parameters. C# supports method overloading after all. Unfortunately, it won't help us. Since the exact class of the node is unknown in advance, the compiler won't be able to determine the correct method to execute. It will default to the method that takes an instance of the base Node class.

However, the Visitor pattern addresses this problem. It uses a technique called Double Dispatch, which helps to execute the proper method on an object without cumbersome conditionals. Instead of letting the client select the method to call, we delegate this choice to the visitor as an argument. Since the objects know their classes, they can pick a proper method for the visitor.

No alt text provided for this image

Now, if we extract a common interface for all visitors, all existing nodes can work with any visitor we introduce into the application. If we find ourselves introducing a new behaviour related to nodes, all we have to do is to implement a new visitor class.

Structuring the Visitor Pattern

In its base implementation the Visitor pattern has five participants:

No alt text provided for this image

  • Visitor: The Visitor interface declares a set of visiting methods that can take concrete elements of an object structure as arguments. These methods may have the same name, but the type of their parameters must be different.
  • Concrete Visitor: Each Concrete Visitor* implements several versions of the same behaviours, tailored for different concrete element classes.
  • Element: The Element interface declares a method for accepting visitors. This method should have one parameter declared with the type of visitor interface.
  • Concrete Element: Each Concrete Element must implement the accept method. The purpose of this method is to redirect the call to the proper visitor's method corresponding to the current element class. Note that if a base element class implements this method, all subclasses must still override this method and call the appropriate method on the visitor object.
  • Client: The Client usually represents a collection or some other complex object (for example, a Composite tree). Usually, clients aren't aware of all the concrete element classes since they work with objects from the collection via some abstract interface.

To demonstrate how the visitor pattern work, we will create an application that creates geometric shapes and export them into XML format.

First, we will create the Element participant, represented by the IShape interface.

No alt text provided for this image

We also need some Concrete Elements which represent the various shapes supported by the application. Note that each element accepts an IVisitor and then triggers the appropriate visitor method. This is called Double Dispatch and we will discuss it further a bit later.

No alt text provided for this image
No alt text provided for this image
No alt text provided for this image

We will also add the ComplexShape class that will represent a compound shape, containing other shapes:

No alt text provided for this image

Now, we can define our Visitor participant. This will be an interface that will define various visit methods, one for each concrete element.

No alt text provided for this image

Finally, we will implement our Concrete Visitor, in this case, an XMLExportVisitor. This class will implement the Visit methods of the IVisitor interface, and create the XML elements for each element:

No alt text provided for this image

To put this all together, in our Main() method we will define a few shapes and then use the XMLExportVisitor to export them in XML format:

No alt text provided for this image

If we run the application, we will see the shapes exported in XML format. Below is the output:

No alt text provided for this image

Double Dispatch and the Visitor Pattern

Double dispatch is a technique that we can use, to control how communication flows between two objects. 

Suppose we are creating an agronomist probe that routinely travels across various and checks soil composition. However, testing the soil composition is different between locations, due to differences in the types of soil found in each location.

Let's model 3 types of soil for simplicity:

No alt text provided for this image

Then we will implement the methods for probing the soil:

No alt text provided for this image
No alt text provided for this image

Now we can deploy our probe:

No alt text provided for this image

And the output will be:

No alt text provided for this image

Thinking as a Compiler

Let's for a moment pretend that we are the compiler, and decide to compile the following code:

No alt text provided for this image

The DisplayName() method is defined in the ISoil interface. But wait for a second, three classes implement the interface. Can we safely decide which of the implementations to call here? It doesn't look so. The only way to know for sure is to launch the application and check the class of an object passed to the method. The only thing we know is that object will have an implementation of the DisplayName() method.

So the resulting compiled code will be checking the class of the object passed to the soil parameter and picking the DisplayName implementation of the appropriate class.

This is called late or dynamic binding. Late because the object and its implementations are linked at runtime. Dynamic because every new object might need to be linked to a different implementation.

Now, let's compile the following code:

No alt text provided for this image

Everything is clear with the first line: the SoilExplorer class doesn't have a custom constructor, so we just instantiate an object. What about the VisitSoil call? The SoilExplorer class has four methods with the same name that differ with parameter types. Which one to call? Looks like we're going to need a dynamic binding here as well.

But there's another problem. What if there's a soil class that doesn't have any appropriate method in the SoilExplorer class? For instance, Clay soil. The compiler can't guarantee that the correct overloaded method exists. An ambiguous situation arises that a compiler can't allow. Therefore, compiler development teams use a safe path and use the early binding for overloaded methods.

This is called early or static binding. Early because it happens at compile time before the application is launched. Static because it can't be altered at runtime.

Let's return to our example. We're sure that the incoming argument will be of the Soil hierarchy: either the ISoil class or one of its subclasses. We also know that the SoilExplorer class has a basic implementation of the VisitSoil that supports the ISoil interface: VisitSoil(ISoil soil).

That's the only implementation that can be safely linked to a given code without making things ambiguous. That's why even if we pass a Podzol object into VisitSoil, the exporter will still call the VisitSoil(ISoil soil) method.

Double Dispatch

Double Dispatch is a technique that allows using dynamic binding alongside overloaded methods.

We can simulate the second level of polymorphism using the Visitor pattern.

First, we need to implement the Accept method in the ISoil classes. The purpose of the Accept method is to give the IProbe more information about which ISoil to Visit via this.

No alt text provided for this image
No alt text provided for this image

Then, we can use the ISoil.Accept method to probe the different types of soils. Note that the Accept method adds an extra layer of indirection. It is there to explicitly tell the Probe which ISoil implementation to visit.

No alt text provided for this image

And the output will be:

No alt text provided for this image

Pros and Cons of Visitor Pattern

No alt text provided for this image

Relations with Other Patterns

  • We can treat the Visitor pattern as a more powerful version of the Command pattern. Its objects can execute operations over various objects of different classes.
  • We can use the Visitor pattern to execute an operation over the entire Composite tree.
  • We can use the Visitor pattern along with the Iterator pattern to traverse a complex data structure and execute some operations over its elements, even if they all have different classes.

Final Thoughts

In this article, we have discussed what is the Visitor pattern, when to use it and what are the pros and cons of using this design pattern. We then examined what is the Double Dispatch technique and how to implement it using the Visitor pattern. Finally, we saw how the Visitor pattern relates to other classic design patterns.

The Visitor design pattern is helpful in many ways and is quite flexible if appropriately used. However, it's worth noting that the Visitor pattern, along with the rest of the design patterns presented by the Gang of Four, is not a panacea or a be-all-end-all solution when designing an application. Once again it's up to the engineers to consider when to use a specific pattern. After all these patterns are useful when used as a precision tool, not a sledgehammer.

To view or add a comment, sign in

More articles by Konstantinos Kalafatis

  • Premises of Software Architecture

    This post is the first of a series of posts around Software Architecture. In this series we are going to discuss what…

  • Adapter Pattern in C#

    Originally posted here The Adapter is a structural design pattern that allows objects with incompatible interfaces to…

  • Observer Pattern in C#

    Originally posted here The Observer is a behavioural design pattern that lets us define a subscription mechanism to…

  • Mediator Pattern in C#

    Originally posted here The Mediator is a behavioural design pattern that lets us reduce chaotic dependencies between…

  • Factory Method Pattern in C#

    Originally posted here The Factory Method is a creational design pattern that allows the abstraction of object…

  • Singleton Pattern in C#

    Originally posted here The Singleton is a creational design pattern that lets us ensure that a class has only one…

  • Composite Pattern in C#

    Originally posted here The Composite is a structural design pattern that allows us to compose objects into tree…

  • Decorator Pattern in C#

    Originally posted here The Decorator is a structural design pattern that lets us attach new behaviours to objects by…

  • Builder Pattern In C#

    Originally posted here The Builder is a creational design pattern that allows us to construct complex objects step by…

    1 Comment
  • Application Caching Strategies

    Originally posted here Most, if not all, developers are at least somewhat familiar with the concept of caching. After…

Others also viewed

Explore content categories