Welcome to the eighth post in the “Design Patterns” series. The previous one was about the template pattern and can be found here.
In this instalment, I want to talk about the visitor pattern. This pattern is used when you want to extend class functionalities, but you try to avoid keeping logic inside of it. This may be due to design choices or the need to organise the code in a more modular way. Both reasons are totally valid.
You can find the code presented in this post right here. The projects in the solution correspond with pattern names.
As usual, we are going to create a short console application. The goal of the application is to present the way visitor pattern allows us to extend class functionalities without modifying their code base. At least without modifying it in noticeable size.
The One (Correct) Way
In all the previous posts we used two different ways: the old and the new one. This time we are going to do it a little bit different. We will start with a base code and work our way up toward the solution we need.
The core of our solution is going to be a set of three classes: one base class and two deriving classes.
This is our base class. It is quite straightforward and contains three properties and a method. The method gives us the gross price by adding a tax value. That's it, really.
As mentioned earlier, there are two classes deriving from the base class.
At this point, there are no specific functionalities assigned to them. The only purpose of them is to have a layer of derived classes. Now that all the elements are in place, it's time to introduce the visitor pattern. The goal of the pattern is very simple: to replace the usage of iteration over the collection of objects. Let’s say we are interested in the prices of products. We can add a method to the class, iterate over the collection of objects and call that method for every object. But that is the point. I don’t want to keep the code of the functionality in a class. I also want the design to be more flexible for future cases.
All of this can be achieved using the visitor pattern. In my humble opinion, it's a compromise between implementing all the functionalities inside of a class and implementing them separately. A very good compromise, if you ask me. But see for yourself.
This is the visitor pattern. Some kind of a visitor should appear then. In our case, it's going to be a class implementing an interface. The interface looks like this:
It has three methods. Two of them are responsible for action during the visit of an object, and the third one displays data aggregated during the visits. The implementation of the interface looks like this:
The class has two basic functionalities. The first functionality is what happens during the object visit. At that moment, we calculate the product's full price, print information and aggregate the value of tax for that product. The second functionality is to display the total value of a tax paid. Looks easy? Good. Because it is.
To make the pattern complete, we also need to mark an object as visitable. To do it, we need to create one more interface.
The interface has only one method. Its goal is to allow accepting a visitor to an implementing class. We need two classes to implement this interface because our goal is to be able to visit both of our products.
The implementation itself is super easy. It uses the visitor object and calls a matching visit method with “this” as an argument. That's it. Those are all the steps that are needed to create a visitor pattern.
Now that all the elements are in place, it's time to use the code in a run scenario. To do it, I want to create a set of objects and visit all of them.
To be able to visit a product object, we need to have a visitor object. In this case, this is going to be a PriceVisitor. After creating the object, we simply need to Accept the visit from this visitor. This can be done inside the ForEach loop.
After completing the process, all that's left is to Display the aggregated data. The results are as follows.
The results are exactly what we expected. In the beginning, we have information about each product. This output was generated while objects were being visited. The last output, the one with the tax value, is the result of data aggregation that took place during visits. It shows us the value of tax added to the products.
The visitor pattern can help us organize class extensibility. Thanks to it we can make it more separate, easier to manage and interface base.
As all single patterns, it should be used in cases where a value can be gained. Have you ever used it? Let me know at firstname.lastname@example.org.