Visitor Design Pattern
Video Lecture
Overview
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Terminology
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Visitor UML Diagram

Source Code
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
./visitor/visitor_concept.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 | # pylint: disable=too-few-public-methods
"The Visitor Pattern Concept"
from abc import ABCMeta, abstractmethod
class IVisitor(metaclass=ABCMeta):
"An interface that custom Visitors should implement"
@staticmethod
@abstractmethod
def visit(element):
"Visitors visit Elements/Objects within the application"
class IVisitable(metaclass=ABCMeta):
"""
An interface the concrete objects should implement that allows
the visitor to traverse a hierarchical structure of objects
"""
@staticmethod
@abstractmethod
def accept(visitor):
"""
The Visitor traverses and accesses each object through this
method
"""
class Element(IVisitable):
"An Object that can be part of any hierarchy"
def __init__(self, name, value, parent=None):
self.name = name
self.value = value
self.elements = set()
if parent:
parent.elements.add(self)
def accept(self, visitor):
"required by the Visitor that will traverse"
for element in self.elements:
element.accept(visitor)
visitor.visit(self)
# The Client
# Creating an example object hierarchy.
Element_A = Element("A", 101)
Element_B = Element("B", 305, Element_A)
Element_C = Element("C", 185, Element_A)
Element_D = Element("D", -30, Element_B)
# Now Rather than changing the Element class to support custom
# operations, we can utilise the accept method that was
# implemented in the Element class because of the addition of
# the IVisitable interface
class PrintElementNamesVisitor(IVisitor):
"Create a visitor that prints the Element names"
@staticmethod
def visit(element):
print(element.name)
# Using the PrintElementNamesVisitor to traverse the object hierarchy
Element_A.accept(PrintElementNamesVisitor)
class CalculateElementTotalsVisitor(IVisitor):
"Create a visitor that totals the Element values"
total_value = 0
@classmethod
def visit(cls, element):
cls.total_value += element.value
return cls.total_value
# Using the CalculateElementTotalsVisitor to traverse the
# object hierarchy
TOTAL = CalculateElementTotalsVisitor()
Element_A.accept(CalculateElementTotalsVisitor)
print(TOTAL.total_value)
|
Output
| python ./visitor/visitor_concept.py
D
B
C
A
561
|
Visitor Example Use Case
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.
Visitor Example UML Diagram

Source Code
./visitor/client.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132 | # pylint: disable=too-few-public-methods
"The Visitor Pattern Use Case Example"
from abc import ABCMeta, abstractmethod
class IVisitor(metaclass=ABCMeta):
"An interface that custom Visitors should implement"
@staticmethod
@abstractmethod
def visit(element):
"Visitors visit Elements/Objects within the application"
class IVisitable(metaclass=ABCMeta):
"""
An interface that concrete objects should implement that allows
the visitor to traverse a hierarchical structure of objects
"""
@staticmethod
@abstractmethod
def accept(visitor):
"""
The Visitor traverses and accesses each object through this
method
"""
class AbstractCarPart():
"The Abstract Car Part"
@property
def name(self):
"a name for the part"
return self._name
@name.setter
def name(self, value):
self._name = value
@property
def sku(self):
"The Stock Keeping Unit (sku)"
return self._sku
@sku.setter
def sku(self, value):
self._sku = value
@property
def price(self):
"The price per unit"
return self._price
@price.setter
def price(self, value):
self._price = value
class Body(AbstractCarPart, IVisitable):
"A part of the car"
def __init__(self, name, sku, price):
self.name = name
self.sku = sku
self.price = price
def accept(self, visitor):
visitor.visit(self)
class Engine(AbstractCarPart, IVisitable):
"A part of the car"
def __init__(self, name, sku, price):
self.name = name
self.sku = sku
self.price = price
def accept(self, visitor):
visitor.visit(self)
class Wheel(AbstractCarPart, IVisitable):
"A part of the car"
def __init__(self, name, sku, price):
self.name = name
self.sku = sku
self.price = price
def accept(self, visitor):
visitor.visit(self)
class Car(AbstractCarPart, IVisitable):
"A Car with parts"
def __init__(self, name):
self.name = name
self._parts = [
Body("Utility", "ABC-123-21", 1001),
Engine("V8 engine", "DEF-456-21", 2555),
Wheel("FrontLeft", "GHI-789FL-21", 136),
Wheel("FrontRight", "GHI-789FR-21", 136),
Wheel("BackLeft", "GHI-789BL-21", 152),
Wheel("BackRight", "GHI-789BR-21", 152),
]
def accept(self, visitor):
for parts in self._parts:
parts.accept(visitor)
visitor.visit(self)
class PrintPartsVisitor(IVisitor):
"Print out the part name and sku"
@staticmethod
def visit(element):
if hasattr(element, 'sku'):
print(f"{element.name}\t:{element.sku}".expandtabs(6))
class TotalPriceVisitor(IVisitor):
"Print out the total cost of the parts in the car"
total_price = 0
@classmethod
def visit(cls, element):
if hasattr(element, 'price'):
cls.total_price += element.price
return cls.total_price
# The Client
CAR = Car("DeLorean")
# Print out the part name and sku using the PrintPartsVisitor
CAR.accept(PrintPartsVisitor())
# Calculate the total prince of the parts using the TotalPriceVisitor
TOTAL_PRICE_VISITOR = TotalPriceVisitor()
CAR.accept(TOTAL_PRICE_VISITOR)
print(f"Total Price = {TOTAL_PRICE_VISITOR.total_price}")
|
Output
| python ./visitor/client.py
Utility :ABC-123-21
V8 engine :DEF-456-21
FrontLeft :GHI-789FL-21
FrontRight :GHI-789FR-21
BackLeft :GHI-789BL-21
BackRight :GHI-789BR-21
Total Price = 4132
|
New Coding Concepts
Instance hasattr()
In the Visitor objects in the example use case above, I test if the elements have a certain attribute during the visit operation.
| def visit(cls, element):
if hasattr(element, 'price'):
...
|
The hasattr()
method can be used to test if an instantiated object has an attribute of a particular name.
| class ClassA():
name = "abc"
value = 123
CLASS_A = ClassA()
print(hasattr(CLASS_A, "name"))
print(hasattr(CLASS_A, "value"))
print(hasattr(CLASS_A, "date"))
|
Outputs
String expandtabs()
When printing strings to the console, you can include special characters \t
that print a series of extra spaces called tabs. The tabs help present multiline text in a more tabular form that appears to be neater to look at.
| abc 123
defg 456
hi 78910
|
The number of spaces added depends on the size of the word before the \t
character in the string. By default, a tab makes up 8 spaces.
Now, not all words separated by a tab will line up the same on the next line.
| abcdef 123
cdefghij 4563
ghi 789
jklmn 1011
|
The problem occurs usually when a word is already 8 or more characters long.
To help solve the spacing issue, you can use the expandtabs()
method on a string to set how many characters a tab will use.
| print("abcdef\t123".expandtabs(10))
print("cdefghij\t4563".expandtabs(10))
print("ghi\t789".expandtabs(10))
print("jklmn\t1011".expandtabs(10))
|
Now outputs
| abcdef 123
cdefghij 4563
ghi 789
jklmn 1011
|
Summary
... Refer to Book, pause Video Lectures or subscribe to Medium Membership to read textual content.