This article is one part of a series about the Visitor pattern. Please read the previous articles to get an overview about the pattern and to get the needed basics to understand this article.
As this article is the last one of the series I want to give a short retrospective and summarize the advantages and disadvantages of the Visitor pattern.
The article series started with a short introduction of the Visitor pattern, shows its strength but also explained the reasons for existing confusions and misunderstandings regarding this pattern. To remove these misunderstandings, we started with an investigation regarding the technical needs for this pattern and explored the difference of single dispatch and double dispatch.
Based on the technical needs we could derive the aims of the pattern. So, the article series continued with explanations and examples for the two main needs: enumeration and dispatching. A further article combined these two aspects to create a wholesome Visitor pattern example.
Based on this Visitor pattern example we investigated some additional questions often seen in practical application of the pattern. We created an extended enumerator Visitor with access to container elements which are not part of the visible objects and we analyzed the pros and cons of a monolithic single Visitor versus small specialized Visitors.
At next there followed articles about topics regarding the code quality. We learned how to create reusable enumerator and query visitors following the separation of concerns rule. And we have seen some examples which can lead to over engineering if we use the Visitor pattern.
Based on these articles I want to summarize the advantages and disadvantages of the pattern. Additional, as the pattern is a good candidate for over engineering, I want to name some use cases for alternative patterns.
Within the articles of this series we have seen some different implementations of the pattern. They all following the same main principles but have some differences in implementation according the practical needs of the respective use cases. Therefore, I don’t want to finish the article with a default implementation of the pattern as there is no singe default implementation. Instead I want to summarize the implementation aspect by list the involved components, the concepts to connect these components and I want to give some recommendations according the naming of the Visitor interfaces and methods.
In object-oriented implementations, data classes often inherit from base classes or interfaces. Data containers which hold collections of these data classes often store pointers to the base classes. This object-oriented concept works fine as long as no type specific methods are needed. In case the client wants to call type specific methods he must cast the base class pointer to the specific type. This may result in code which is hard to understand, hard to maintain and has a good chance for errors.
The Visitor pattern resolves this issue. It allows a type safe access to the origin data type without the need of casting. Furthermore, the Visitor pattern offers traversing mechanism. As result, the pattern allows an easy access of data elements independent whether they are stored in a simple list or a complex container and independent whether they are stored in origin type or as pointers to any base type.
It isn’t easy to find the right balance between many small specialized Visitor interfaces and a few or a single monolithic wide Visitor interface. And both approaches came with drawbacks. Many small interfaces increasing the complexity of the system and few wide interfaces lead to implementations containing empty method implementations.
The Visitor pattern traverse elements of a data container. This traversing is done via double dispatch. As a result of this dispatching, the traversing is some kind of hidden feature. A client implements the dispatching callbacks but he may easily overlock that the callback is one step of a traversing over a data container. On one hand that’s fine because the client should not have to think about the data structures, but on the other hand this may cause issues. Like in any other traversing mechanism, the client should not change the data container during traversing. So, the dispatching callback methods of the client should not change the data container itself. Furthermore, a traversing step should normally not execute long running complex functionalities. In summary, the traversing is some kind of hidden feature for a client developer, but it is an important software design fact which has to be respected by the client developer. This contradiction may result in bad software designs, especially if the developer isn’t that familiar with the Visitor pattern.
The Visitor pattern is a powerful but complex pattern. It unites two functionalities: type conversation by dispatching and traversing of complex data structures. But if only one of these functionalities is needed, you will find more specialized patterns which are less complex. If you want to traverse over a data structure only, you should implement an Iterator pattern. And if you want to do the dispatching only, you may use Callback mechanisms.
Components and Dependencies
The Visitor pattern contains the following components.
- Data Elements
- Data Container
The Data elements are the Visitable components and the Enumerator and Algorithm together are the Visitor component which execute some functionality based on the data elements. As explained and shown within the article series, I prefer a clear separation of concerns. Therefore, I recommend implementing the Algorithm aspect and the Enumeration aspect in different and independent components. Different Algorithms and different Enumeration can be loosely coupled by composition according the use case specific needs. A strong coupling by using inheritance should only be used in case there is no need for a reuse of any of the components.
At the very beginning of the article series I mentioned that, in my opinion, the Visitor pattern is one of the most misused and misunderstand patterns and the main source of this issue is the misleading naming of the pattern interfaces and methods.
Names of methods, interfaces and components should reflect their purpose. Therefore, I recommend avoiding the naming which is used within the origin Visitor pattern as this naming is meaningless. Following I would give you some naming I like to use. But of course, feel free to find your own naming.
As the name Visitor is well known and most software developers know the pattern, you should use this name anyway, even if it is universal and therefore some kind of meaningless. You should use the name but extend it with a more describing naming. As mentioned before, we have a separation of concerns and we may create specialized visitors. Therefore, we may create visitors for following purposes: enumeration, queries, data updates and so on. So, you should not implement a “IVisitor” interface or a “Visitor” component, but specialized ones like a “CustomerEnumerationVisitor” or a “OrderQueryVisitor”. Some may argue that implementation details should not part of naming. That’s totally fine, but as the Visitor contains the hidden enumeration feature it may be very useful for a client developer to know that he uses a component which is implemented as Visitor. Therefore, in this case it is fine to add the “Visitor” prefix to the component name.
The data elements will be passed to the Visitors. Therefore, I would call them “Visitable”. This results in according interface names like “IVisitableCustomer” or “IVisitableOrder”.
But what’s with the method names? The origin method names are “accept” and “visit”. To be honest, I think these names are terrible. They don’t say anything about their purpose. So, let’s think about the purpose of the methods and find some better names. The Visitor pattern is implemented by double dispatch. For this purpose, the data elements offer a method to get the instance of the data element. This getter method will pass the element instance to an instance receiver method which is provided by the Visitor. The method names should reflect this dispatching process. So, let’s call the method to get the element instance “GetInstance” and call the method which receives these instance “InstanceReceiver”.
By using such a clear naming, you could avoid some of the misunderstanding and errors which are a result of the complexity of the Visitor pattern.
The Visitor pattern is one of the base implementation patterns in software development. So, it should be part of the tool kit of a good software engineer. But unfortunately, the Visitor Pattern comes with a big disadvantage: it is one of the most misunderstood and misused patterns I know. This article series provided an extensive overview about the Visitor pattern and give you the needed knowledge to use the Visitor pattern within your applications.