- Joined
- 6/4/22
- Messages
- 40
- Points
- 118
Follow along with the video below to see how to install our site as a web app on your home screen.
Note: This feature may not be available in some browsers.
# 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 Professor. Does your Advanced C++ course cover it?Visitor is super. I invented indepedently in 1992 and built a complete CAD package around it. Separation of Concerns.
Yes, in fact we do all the patterns in that course!Thanks Professor. Does your Advanced C++ course cover it?
// 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();
}
}
}
// 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("]");
}
}
Thanks again, you're awesome!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 a lot Professor! I've seen many interesting topics in the cause and would definitely need to study into 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
and ze new one..
Visitor is like an outsourced member function.
![]()
New Book: Multiparadigm Design Patterns in Python, C++ and C# by Daniel J. Duffy and Harold Kasperink (Datasim Press December 2023)
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 Summary The main...quantnet.com
@APalley
// 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();
}
#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;
}