BLOG
09 June 2022

Design Patterns: The Visitor Pattern

tech

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.

The Goal

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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Product { public int Id; public string Name; public double Price; public Product(int id, string name, double price) { Id = id; Name = name; Price = price; } public double GetPrice(double incomeTaxRate) => Math.Round(Price * (1 + incomeTaxRate), 2); }

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.

1 2 3 4 5 6 public class NormalProduct: Product { public NormalProduct(int id, string name, double price) : base(id, name, price) { } }
1 2 3 4 5 6 public class PremiumProduct: Product { public PremiumProduct(int id, string name, double price) : base(id, name, price) { } }

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:

1 2 3 4 5 6 7 8 namespace VisitorPattern.Interfaces; public interface IVisitor { void VisitNormalProduct(NormalProduct normalProduct); void VisitPremiumProduct(PremiumProduct premiumProduct); void Display(); }

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:

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 namespace VisitorPattern.VisitorClasses; public class PriceVisitor: IVisitor { private double _taxPayed = 0; public void VisitNormalProduct(NormalProduct normalProduct) { var totalPrice = normalProduct.GetPrice(0.1); Console.WriteLine($"Normal Product Info ID: {normalProduct.Id}, Name: {normalProduct.Name} and the Price {totalPrice}"); _taxPayed += totalPrice - normalProduct.Price; } public void VisitPremiumProduct(PremiumProduct premiumProduct) { var totalPrice = premiumProduct.GetPrice(0.3); Console.WriteLine($"Normal Product Info ID: {premiumProduct.Id}, Name: {premiumProduct.Name} and the Price {totalPrice}"); _taxPayed += totalPrice - premiumProduct.Price; } public void Display() { Console.WriteLine($"Tax payed: {_taxPayed}"); } }

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.

1 2 3 4 public interface IVisitableElement { void Accept(IVisitor visitor); }

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.

1 2 3 4 5 6 7 8 9 10 11 12 13 namespace VisitorPattern.Entities; public class NormalProduct: Product, IVisitableElement { public NormalProduct(int id, string name, double price) : base(id, name, price) { } public void Accept(IVisitor visitor) { visitor.VisitNormalProduct(this); } }

 

1 2 3 4 5 6 7 8 9 10 11 12 13 namespace VisitorPattern.Entities; public class PremiumProduct: Product, IVisitableElement { public PremiumProduct(int id, string name, double price) : base(id, name, price) { } public void Accept(IVisitor visitor) { visitor.VisitPremiumProduct(this); } }

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.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void TheWay() { var products = new List<IVisitableElement> { new NormalProduct(1, "First Normal Product", 100), new NormalProduct(2, "Second Normal Product", 250), new PremiumProduct(3, "First Premium Product", 600), new PremiumProduct(4, "Second Premium Product", 700) }; var priceVisitor = new PriceVisitor(); products.ForEach(p => p.Accept(priceVisitor)); priceVisitor.Display(); }

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.

Summary

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 karol.rogowski@softwarehut.com.

Need an experienced specialist?


Author
Karol Rogowski
Head Of Engineering

Working in IT since 2009. Currently working as Head of Engineering at SoftwareHut and as an academic teacher at Białystok Technical University. Co-founder of meet.js Białystok. Book and articles author. Father, husband, huge H.P. Lovecraft fan and terrible poker player.