# 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