Author image

Visitor Design Pattern


Difficulty:
2/5


I have to agree that this design pattern confused me for a longer time than I'd want to admit. But fear not! Confusion is now over! The time of Understanding is at hand! This cancer curing project will solve all mysteries for the Visitor pattern.

Definition: Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing (recompiling) the classes of the elements on which it operates.

Understood? No? I didn't think so; keep on reading. I never understood those definitions either. They only make sense After you delve deep into it and start making sense of it on your terms!

A Visitor is a behavioral software design pattern and a good design decision if you want to perform similar kinds of operations on objects of different types that are grouped inside a structure.

Design

  • Create a Visitor abstract base class that defines a pure virtual visit() method. Each visit method accepts a single argument, a pointer or reference to an original Element derived class.
  • Create Visitor sub-classes, one subclass for each operation you want to perform on the Element objects.
  • Add a single pure virtual accept( Visitor&  v) method to the base class of the Element (the class you want to operate on) hierarchy. accept is defined to receive a single argument, a (non-const) pointer or reference to the abstract base class Visitor. Preferably a reference such that you can also pass it temporaries.
  • Each concrete derived class of Element implements the accept method by simply calling the visit method on the Visitor object parameter, passing its this pointer as the sole argument.
  • When a client needs an operation to be performed on a derived Element class he creates an instance of the Visitor object, calls the accept method on the Element object and passes the visitor. If the client wants a new operation to be performed on Element types he simply creates a new subclass of Visitor.

I urge you to look at the example (make sure main_visited.cpp and with_visitor.cpp are excluded from compilation first by selecting them in Visual Studio -> right click -> properties -> General -> Excluded from Build: write “Yes” ). Then compile check the code and question yourself. Then do the same with the other two .cpp files. Use the visitor. You can do this. I believe in you.

When to use it?

You should use visitors when the following conditions hold:

  • you have a well-defined, known set of classes which will be visited (operated on).
  • operations on said classes are not well-defined or known in advance. For example, if someone is consuming your API and you want to give consumers a way to add new ad-hoc functionality to objects. They're also a convenient way to extend sealed classes with ad-hoc functionality.
  • you perform operations of a class of objects and want to avoid run-time type testing. This is usually the case when you traverse a hierarchy of disparate objects having different properties.

How it works?

When the accept method is called in the program, its implementation is chosen based on both the dynamic type of the element and the dynamic type of the visitor. When the associated visit method is called, its implementation is chosen based on both the dynamic type of the visitor and the dynamic type of the element argument. If the visitor can't handle an argument of the given element's type, then the compiler will catch the error - major plus.

This is known as double dispatch. Double-dispatch is the ability to choose which version of a function to call based on the runtime type of the object performing the call as well as the runtime type of the arguments passed to the function call.

Without double dispatch we are left with the traditional algorithm for virtually dispatching objects in OOP languages. This algorithm is used by the compiler to figure out which overridden and/or overloaded method will be chosen upon a virtual function call. You can apply that yourself. It's not that hard.

In ascending level of precedence:

  1. look at the static type of the object that makes the call (left of the . or ->)
  2. look at the dynamic type of the object that makes the call
  3. look (only) at the static type of the arguments

What double dispatch allows us to do, is to also consider the dynamic/runtime type of the argument when deciding which function to call! YEEHAH!

Advanced case: For a visitor with additional arguments in his visit method. For example when you'd want to supply more data to perform your desired operation on an Element then you'd best pass these arguments to your particular concrete Visitor's constructor.

A good example works wonders! We illustrate how to do double dispatch without a visitor and then with a visitor. Compare and contrast and imagine the possibilities of what you and a Visitor can accomplish if you work togetheawr!

I've used Windows 8.1 x86_64, Visual Studio 2017 as always to compile the project.

Github

Github repository link.


0 likes