• C++ Programming for Financial Engineering
    Highly recommended by thousands of MFE students. Covers essential C++ topics with applications to financial engineering. Learn more Join!
    Python for Finance with Intro to Data Science
    Gain practical understanding of Python to read, understand, and write professional Python code for your first day on the job. Learn more Join!
    An Intuition-Based Options Primer for FE
    Ideal for entry level positions interviews and graduate studies, specializing in options trading arbitrage and options valuation models. Learn more Join!

What is a Visitor Pattern?

Joined
6/4/22
Messages
40
Points
118
I was asked with this question in a quant strategist position interview.
Can someone briefly explain what is a Visitor Pattern? What is the purpose/benefit of it? And an example in C++ code?

Also, could you provide suggestions on how to study design pattern? Especially in C++ practically?
 
Visitor is super. I invented indepedently in 1992 (!) and built a complete CAD package around it. Separation of Concerns.
(originally called multiple polymorphism, invented by Dan Ingalls). In my many books and courses.

A visitor pattern is a software design pattern and separates the algorithm from the object structure. Because of this separation new operations can be added to existing object structures without modifying the structures.

Thus, extend functionality w/o having to pull out the kitchen pipes.


Example

C++:
# Visitor.py

''''
Visitor is a behavioural pattern that allows functionality/extra behaviour
to be added to the classes of a class hierarchy in a non-intrusive manner. It
was invented by Dan Ingalls in for the Smalltalk language
https://dl.acm.org/doi/abs/10.1145/960112.28732

Initial Remarks
1. Many developers haven't come to grips with Visitor for a number of reasons
that we discuss elsewhere. Anecdotally, one of the authors of the GOF book stated
that he used all the patterns except Visitor. He saw no use for Visitor. Why is that?
2. DJD invented Visitor in 1992 independently of GOF when designing C++ libraries for Computer Aided
Design), engineering and optical technology. We created a basic class hierarchy. Extra functionality to suit new user
requirements is easily achieved by independent visitor classes. Composites and Visitor go well together.
3. In this way, we avoid monolithic class libraries and the Single Responsibility Principle (SRP)
is automtically guaranteed. Flexibility can be added to the visitor hierarchy by applying the other GOF
patterns to it.
4. It is possble to "misuse" Visitor so that it be used as Command and Factory-like
(serialisation/deserialisation) pattern. This enables cutting down on "design pattern footprint".
5. Python is useful vehicle for learning design patterns. Once you understand the concepts and the prototype
it is then possible to write production versions in C++ and C#.

6. In C++, the Visitor pattern is a workaround functionality that is missing in the language,
that is multi-methods. A multi-method is a mechanism for a virtual function calls based on more
than one object. More generally, it is called multiple dispatch and is supported in languages such
as CLOS and Julia. We are particularly interested in double-dispatch mechanism in which a function has two
polymorhic arguments as it were. In the case of adding functionality to an existing class hierarchy
(the goal of Visitor), the first argument is an object and the second argumet is an operation:

    dispatch(Object, Operation) (as discussed in Ingalls' 1986 OOPSLA article)

This syntax is not supported in C++ and Visitor provides a workaround as obj.accept(visitor)
or visitor.Visit(obj).
The problem with a naive implementation of Visitor is that we need to deal with source code dependency
cycles (mainly between the classes in the Object hierarchy). There are several ways to resolve this problem,
one of which is the so-called (and outdated) *Acyclic Visitor* which dates from the early days of OOP when
subtype polymorphism (virtual functions) and multiple inheritance were considered to be the gold standard.
Such solutions are not suitable for hard real time applications because of dynamic casting in C++.
Furthermore, *Acyclic Visitor* leads to a tsunami of unmaintainable classes in object networks.

7. In C#, the Visitor pattern can be simplified by calling statically known methods with dynamically typed
arguments. This defers member overload resolution from compile time to runtime.


    

the first a
'''

# DJD 2022-10-5 #5


from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
import copy

# Context hierarchy which will get Visitor functionality
class Component(ABC):
 
    @abstractmethod
    def accept(self, visitor: Visitor) -> None:
        pass


class ConcreteComponentA(Component):
 
    def accept(self, visitor: Visitor) -> None:
    
        visitor.visit(self)


class ConcreteComponentB(Component):
 
    def accept(self, visitor: Visitor):
        visitor.visit(self)


class ConcreteComponentC(Component):
    def __init__(self):
        self.val = None
 
    @classmethod
    def Create(cls):
        return cls(999)

    def accept(self, visitor: Visitor):
        visitor.visit(self)

    def set(self, val):
         self.val = val

    def print(self):
        print("C ", self.val)

# Visitor hierarchy
class Visitor(ABC):
  
    @abstractmethod
    def visit(self, element: ConcreteComponentA) -> None:
        pass

    @abstractmethod
    def visit(self, element: ConcreteComponentB) -> None:
        pass


class ConcreteVisitor1(Visitor):
    def visit(self, element: ConcreteComponentA ) -> None:
        print("ConcreteVisitor1")

    def visit(self, element : ConcreteComponentB) -> None:
        print("ConcreteVisitor1")


class ConcreteVisitor2(Visitor):
    def visit(self, element : ConcreteComponentA) -> None:
        print("ConcreteVisitor2")

    def visit(self, element : ConcreteComponentB ) -> None:
        print("ConcreteVisitor2")

# A visitor is masquerading as a factory/init method
class ConcreteVisitor3(Visitor):
    def visit(self, element : ConcreteComponent) -> None:
        element.set(999)

def Test(components: List[Component], visitor: Visitor) -> None:
 
    # Apply a single visitor to a set of components 
    print("Visitor using accept()")
    for component in components:
        component.accept(visitor)
 
    print("Visitor using visit()")
    for component in components:
        visitor.visit(component)

  
def Test2(visitors: List[Visitor], component: Component) -> None:
 
    # Apply a chain of visitors to/on a single component
    print("Visitor using accept()")
    for visitor in visitors:
        component.accept(visitor)
 
    print("Visitor using visit()")
    for visitor in visitors:

        visitor.visit(component)
# EXX create visitors for composites

if __name__ == "__main__":
    print("1 visitor applied to a list of components")
    components = [ConcreteComponentA(), ConcreteComponentB()]

    visitor1 = ConcreteVisitor1()
    Test(components, visitor1)

    visitor2 = ConcreteVisitor2()
    Test(components, visitor2)
 
    print("A list of visitors applied to 1 component")
    visitors = [visitor1, visitor2]
    component = ConcreteComponentA()
    Test2(visitors, component)

    # Visitor as an object initialisor
    component = ConcreteComponentC(); component.print() # None
    v = ConcreteVisitor3()
    v.visit(component)
    component.print() # 999
 
Last edited:
Thanks Professor. Does your Advanced C++ course cover it?
Yes, in fact we do all the patterns in that course!
Wouldn't be surprised if interviewer had seen my previous books and articles :cool:
and ze new one..

Visitor is like an outsourced member function.


@APalley
 
Last edited:
C++:
// ShapeVisitor.cs
//
// Abstract base class for shape visitors.
// The visitor can be used to extend the functionality of
// the shape hierarchy such as output drivers, transformation drivers, etc.
// Can be used in combination with Composites.
//
// (C) Datasim Education BV  2001-2002

public abstract class ShapeVisitor
{
    // Default constructor
    public ShapeVisitor()
    {
    }

    // Copy constructor
    public ShapeVisitor(ShapeVisitor source)
    {
    }

    // Visit point.
    public abstract void Visit(Point p);

    // Visit circle.
    public abstract void Visit(Circle c);
    
    // Visit line.
    public abstract void Visit(Line l);
    
    // Visit shape composite.
    public virtual void Visit(ShapeComposite c)
    {
        // Use iterator to traverse composite
        IShapeIterator it = c.Begin();
        IShapeIterator end = c.End();
        while( !it.Equals(end) )
        {
            Shape current = it.GetCurrent();
            current.Accept(this);
            it.Next();
        }
    }
}
 
C++:
// ConsoleVisitor.cs
//
// Shape visitor that sends the shapes to the console output.
//
// (C) Datasim Education BV  2001-2002

using System;

public class ConsoleVisitor: ShapeVisitor
{
    // Default Constructor.
    public ConsoleVisitor(): base()
    {
    }

    // Copy constructor.
    public ConsoleVisitor(ConsoleVisitor source): base(source)
    {
    }

    // Visit a point
    public override void Visit(Point p)
    {
        Console.WriteLine("[Point: ({0}, {1})]", p.X, p.Y);
    }

    // Visit a circle
    public override void Visit(Circle c)
    {
        Console.WriteLine("[Circle: ");
        Visit(c.CenterPoint);    // Visit centre point.
        Console.WriteLine(", {0}]", c.Radius);
    }

    // Visit a line
    public override void Visit(Line l)
    {
        Console.WriteLine("[Line: ");
        Visit(l.P1);        // Visit first point.
        Visit(l.P2);        // Visit second point.
        Console.WriteLine("]");
    }

    // Visit a shape composite.
    public override void Visit(ShapeComposite c)
    {

        Console.WriteLine("[Composite: ");

        // Use iteration code from base visitor class
        base.Visit(c);

        Console.WriteLine("]");
    }
}
 
Visitor is super. I invented indepedently in 1992 (!) and built a complete CAD package around it. Separation of Concerns.
(originally called multiple polymorphism, invented by Dan Ingalls). In my many books and courses.

A visitor pattern is a software design pattern and separates the algorithm from the object structure. Because of this separation new operations can be added to existing object structures without modifying the structures.

Thus, extend functionality w/o having to pull out the kitchen pipes.


Example

C++:
# Visitor.py

''''
Visitor is a behavioural pattern that allows functionality/extra behaviour
to be added to the classes of a class hierarchy in a non-intrusive manner. It
was invented by Dan Ingalls in for the Smalltalk language
https://dl.acm.org/doi/abs/10.1145/960112.28732

Initial Remarks
1. Many developers haven't come to grips with Visitor for a number of reasons
that we discuss elsewhere. Anecdotally, one of the authors of the GOF book stated
that he used all the patterns except Visitor. He saw no use for Visitor. Why is that?
2. DJD invented Visitor in 1992 independently of GOF when designing C++ libraries for Computer Aided
Design), engineering and optical technology. We created a basic class hierarchy. Extra functionality to suit new user
requirements is easily achieved by independent visitor classes. Composites and Visitor go well together.
3. In this way, we avoid monolithic class libraries and the Single Responsibility Principle (SRP)
is automtically guaranteed. Flexibility can be added to the visitor hierarchy by applying the other GOF
patterns to it.
4. It is possble to "misuse" Visitor so that it be used as Command and Factory-like
(serialisation/deserialisation) pattern. This enables cutting down on "design pattern footprint".
5. Python is useful vehicle for learning design patterns. Once you understand the concepts and the prototype
it is then possible to write production versions in C++ and C#.

6. In C++, the Visitor pattern is a workaround functionality that is missing in the language,
that is multi-methods. A multi-method is a mechanism for a virtual function calls based on more
than one object. More generally, it is called multiple dispatch and is supported in languages such
as CLOS and Julia. We are particularly interested in double-dispatch mechanism in which a function has two
polymorhic arguments as it were. In the case of adding functionality to an existing class hierarchy
(the goal of Visitor), the first argument is an object and the second argumet is an operation:

    dispatch(Object, Operation) (as discussed in Ingalls' 1986 OOPSLA article)

This syntax is not supported in C++ and Visitor provides a workaround as obj.accept(visitor)
or visitor.Visit(obj).
The problem with a naive implementation of Visitor is that we need to deal with source code dependency
cycles (mainly between the classes in the Object hierarchy). There are several ways to resolve this problem,
one of which is the so-called (and outdated) *Acyclic Visitor* which dates from the early days of OOP when
subtype polymorphism (virtual functions) and multiple inheritance were considered to be the gold standard.
Such solutions are not suitable for hard real time applications because of dynamic casting in C++.
Furthermore, *Acyclic Visitor* leads to a tsunami of unmaintainable classes in object networks.

7. In C#, the Visitor pattern can be simplified by calling statically known methods with dynamically typed
arguments. This defers member overload resolution from compile time to runtime.


   

the first a
'''

# DJD 2022-10-5 #5


from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
import copy

# Context hierarchy which will get Visitor functionality
class Component(ABC):
 
    @abstractmethod
    def accept(self, visitor: Visitor) -> None:
        pass


class ConcreteComponentA(Component):
 
    def accept(self, visitor: Visitor) -> None:
   
        visitor.visit(self)


class ConcreteComponentB(Component):
 
    def accept(self, visitor: Visitor):
        visitor.visit(self)


class ConcreteComponentC(Component):
    def __init__(self):
        self.val = None
 
    @classmethod
    def Create(cls):
        return cls(999)

    def accept(self, visitor: Visitor):
        visitor.visit(self)

    def set(self, val):
         self.val = val

    def print(self):
        print("C ", self.val)

# Visitor hierarchy
class Visitor(ABC):
 
    @abstractmethod
    def visit(self, element: ConcreteComponentA) -> None:
        pass

    @abstractmethod
    def visit(self, element: ConcreteComponentB) -> None:
        pass


class ConcreteVisitor1(Visitor):
    def visit(self, element: ConcreteComponentA ) -> None:
        print("ConcreteVisitor1")

    def visit(self, element : ConcreteComponentB) -> None:
        print("ConcreteVisitor1")


class ConcreteVisitor2(Visitor):
    def visit(self, element : ConcreteComponentA) -> None:
        print("ConcreteVisitor2")

    def visit(self, element : ConcreteComponentB ) -> None:
        print("ConcreteVisitor2")

# A visitor is masquerading as a factory/init method
class ConcreteVisitor3(Visitor):
    def visit(self, element : ConcreteComponent) -> None:
        element.set(999)

def Test(components: List[Component], visitor: Visitor) -> None:
 
    # Apply a single visitor to a set of components
    print("Visitor using accept()")
    for component in components:
        component.accept(visitor)
 
    print("Visitor using visit()")
    for component in components:
        visitor.visit(component)

 
def Test2(visitors: List[Visitor], component: Component) -> None:
 
    # Apply a chain of visitors to/on a single component
    print("Visitor using accept()")
    for visitor in visitors:
        component.accept(visitor)
 
    print("Visitor using visit()")
    for visitor in visitors:

        visitor.visit(component)
# EXX create visitors for composites

if __name__ == "__main__":
    print("1 visitor applied to a list of components")
    components = [ConcreteComponentA(), ConcreteComponentB()]

    visitor1 = ConcreteVisitor1()
    Test(components, visitor1)

    visitor2 = ConcreteVisitor2()
    Test(components, visitor2)
 
    print("A list of visitors applied to 1 component")
    visitors = [visitor1, visitor2]
    component = ConcreteComponentA()
    Test2(visitors, component)

    # Visitor as an object initialisor
    component = ConcreteComponentC(); component.print() # None
    v = ConcreteVisitor3()
    v.visit(component)
    component.print() # 999
Thanks again, you're awesome!
 
Yes, in fact we do all the patterns in that course!
Wouldn't be surprised if interviewer had seen my previous books and articles :cool:
and ze new one..

Visitor is like an outsourced member function.


@APalley
Thanks a lot Professor! I've seen many interesting topics in the cause and would definitely need to study into it.
 
A good way to learn patterns and reduce learning curve is to do first do Visitor in Python and then port to C++.

Modern Multiparadigm Software Architectures and Design Patterns
with Examples and Applications in C++, C# and Python Volume I

Datasim Press (planned publication date December 2023)


Daniel J. Duffy dduffy@datasim.nl and Harold Kasperink harold.kasperink@enjoytocode.com
 
I have subsumed the GOF pattern in C++20, making them more robust and future-proof.
In our new book.

C++:
// TestFactories.cpp
//
// Factory patterns in C++20 Concepts
//
// (C) Datasim Education BV 2023
//
#include <iostream>

template <typename Factory>
    concept ICreate = requires (Factory& f)
{
    f.Create();
};

template <typename T>
    struct MyFactory
{
    T Create()
    {
        return T();
    }
};

template <typename Factory, typename Class> Class myCreate(Factory f)
                    requires ICreate<Factory>
{
    return f.Create();
}

struct C
{
    int n;
    C() : n(0) { std::cout << " C is born\n"; }
};


int main()
{
    MyFactory<C> fac;
    C c = fac.Create();

}
 
The Visitor Pattern is a behavioral design pattern used in object-oriented programming. It is primarily used when you have a set of classes representing various types of objects, and you want to perform different operations on these objects without modifying their class definitions. The main purpose of the Visitor Pattern is to separate the algorithm from the object structure on which it operates. It allows you to add new operations or functionalities to classes without altering their code.
Example in C++:

Here's a simplified example of the Visitor Pattern in C++:

#include <iostream>

// Forward declaration of classes to avoid circular dependencies
class ConcreteElementB;
class ConcreteElementA;

// Visitor base class
class Visitor {
public:
virtual void visit(ConcreteElementA* element) = 0;
virtual void visit(ConcreteElementB* element) = 0;
};

// Element base class
class Element {
public:
virtual void accept(Visitor* visitor) = 0;
};

// Concrete elements
class ConcreteElementA : public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}

void operationA() {
std::cout << "ConcreteElementA operation." << std::endl;
}
};

class ConcreteElementB : public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}

void operationB() {
std::cout << "ConcreteElementB operation." << std::endl;
}
};

// Concrete visitor
class ConcreteVisitor : public Visitor {
public:
void visit(ConcreteElementA* element) override {
element->operationA();
}

void visit(ConcreteElementB* element) override {
element->operationB();
}
};

int main() {
ConcreteElementA elementA;
ConcreteElementB elementB;
ConcreteVisitor visitor;

elementA.accept(&visitor); // Invokes ConcreteElementA's operation
elementB.accept(&visitor); // Invokes ConcreteElementB's operation

return 0;
}
 
you can use "insert code" option ;)


C++:
#include <iostream>

// Forward declaration of classes to avoid circular dependencies
class ConcreteElementB;
class ConcreteElementA;

// Visitor base class
class Visitor {
public:
virtual void visit(ConcreteElementA* element) = 0;
virtual void visit(ConcreteElementB* element) = 0;
};

// Element base class
class Element {
public:
virtual void accept(Visitor* visitor) = 0;
};

// Concrete elements
class ConcreteElementA : public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}

void operationA() {
std::cout << "ConcreteElementA operation." << std::endl;
}
};

class ConcreteElementB : public Element {
public:
void accept(Visitor* visitor) override {
visitor->visit(this);
}

void operationB() {
std::cout << "ConcreteElementB operation." << std::endl;
}
};

// Concrete visitor
class ConcreteVisitor : public Visitor {
public:
void visit(ConcreteElementA* element) override {
element->operationA();
}

void visit(ConcreteElementB* element) override {
element->operationB();
}
};

int main() {
ConcreteElementA elementA;
ConcreteElementB elementB;
ConcreteVisitor visitor;

elementA.accept(&visitor); // Invokes ConcreteElementA's operation
elementB.accept(&visitor); // Invokes ConcreteElementB's operation

return 0;
}
 
Back
Top