As a material for learning GoF design patterns, the book "Introduction to Design Patterns Learned in the Augmented and Revised Java Language" seems to be helpful. However, since the examples taken up are based on JAVA, I tried the same practice in Python to deepen my understanding.
The Visitor pattern is a design pattern for separating an algorithm from the structure of an object in object-oriented programming and software engineering. As a practical result of the separation, new operations on existing objects can be added without changing the structure. Basically, the Visitor pattern allows you to add new virtual functions to a group of classes without changing the class itself. To do this, create a Visitor class that properly specializes in all virtual functions. The Visitor takes a reference to the instance as input and uses double dispatch to reach its goal. Visitors are powerful, but they also have limitations compared to existing virtual functions. You need to add a small callback method within each class, and the callback method of each class cannot be inherited by a new subclass.
UML class and sequence diagram

UML class diagram

This is a quote from the book "Introduction to Design Patterns Learned in the Java Language", but I was hungry.
Visitor means "visitor". Let's say you have a lot of elements stored in your data structure and you want to do some "processing" on each of them. Where should the "processing" code be written at this time? If you think about it normally, you write it in a class that represents a data structure. But what if that "processing" isn't always one type? In that case, the data structure class would have to be modified each time new processing was needed. The
Visitorpattern ** separates data structures and processing **. Then, prepare a class that represents the "visitor" who is the main body that walks around the data structure, and let that class handle the processing. Then, when you want to add a new process, you can create a new "visitor". And the data structure should accept the "visitors" who hit the door.
I would like to actually run a sample program that utilizes the Visitor pattern and check the following behavior. By the way, the behavior is the same as the sample program in Qiita article "Learn the design pattern" Composite "with Python", so compare the implementations. This will give you a better understanding of the Visitor pattern.
--Try adding subdirectories and files to the directory of the root entry
--Try adding the directory of the user entry to the directory of the root entry, and then the subdirectories and files.
--Dare to add a directory to the file and make sure it fails
$ python Main.py
Making root entries
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)
Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/yuki (300)
/root/usr/yuki/diary.html (100)
/root/usr/yuki/composite.py (200)
/root/usr/hanako (300)
/root/usr/hanako/memo.tex (300)
/root/usr/tomura (900)
/root/usr/tomura/game.doc (400)
/root/usr/tomura/junk.mail (500)
Occurring Exception...
FileTreatmentException
Similar code has been uploaded to the Git repository. https://github.com/ttsubo/study_of_design_pattern/tree/master/Visitor
--Directory structure
.
├── Main.py
└── visitor
├── __init__.py
├── element.py
└── visitor.py
The Visitor role declares a visit (xxxx) method that says" visited xxxx "for each specific element of the data structure (ConcreteElement role). visit (xxxx) is a method for processing xxxx. The actual code is written on the side of the Concrete Visitor role.
In the sample program, the Visitor class serves this role.
visitor/visitor.py
from abc import ABCMeta, abstractmethod
class Vistor(metaclass=ABCMeta):
@abstractmethod
def visit(self, directory):
pass
The ConcreteVisitor role implements the interface for the Visitor role. Implement a method of the form visitor (xxxx) and describe the processing for each ConcreteElement role.
In the sample program, the ListVistor class serves this role.
visitor/visitor.py
class ListVistor(Vistor):
def __init__(self):
self.__currentdir = ''
def visit(self, directory):
print("{0}/{1}".format(self.__currentdir, directory))
if isinstance(directory, Directory):
savedir = self.__currentdir
self.__currentdir = self.__currentdir + '/' + directory.getName()
for f in directory:
f.accept(self)
self.__currentdir = savedir
The role of ʻElement is a role that represents the destination of the role of Visitor. Declare the ʻaccept method that accepts the visit. The Visitor role is passed as an argument to the ʻaccept method. In the sample program, the ʻElement class serves this role.
visitor/element.py
from abc import ABCMeta, abstractmethod
class Element(metaclass=ABCMeta):
@abstractmethod
def accept(self, v):
pass
The ConcreteElement role is the role that implements the interface for the ʻElement role. In the sample program, the ʻEntry class, the File class, and the Directory class serve this role.
visitor/element.py
class Entry(Element):
@abstractmethod
def getName(self):
pass
@abstractmethod
def getSize(self):
pass
def add(self, entry):
raise FileTreatmentException
def __str__(self):
return "{0} ({1})".format(self.getName(), self.getSize())
class File(Entry):
def __init__(self, name, size):
self.__name = name
self.__size = size
def getName(self):
return self.__name
def getSize(self):
return self.__size
def accept(self, v):
v.visit(self)
class Directory(Entry):
def __init__(self, name):
self.__name = name
self.__dir = []
def getName(self):
return self.__name
def getSize(self):
size = 0
for f in self.__dir:
size += f.getSize()
return size
def add(self, entry):
self.__dir.append(entry)
return self
def __iter__(self):
self.__index = 0
return self
def __next__(self):
if self.__index >= len(self.__dir):
raise StopIteration()
dir = self.__dir[self.__index]
self.__index += 1
return dir
def accept(self, v):
v.visit(self)
The ʻObject Structure role is a role that handles a set of ʻElement roles. It has methods that allow the ConcreteVisitor role to handle individual ʻElementroles. In the sample program, theDirectoryclass serves this role. (Two roles per person) TheDirectory class of the sample program provides an ʻiterator so that the ConcreteVisitor role can handle individual ʻElement` roles.
In the sample program, the startMain method serves this role.
Main.py
from visitor.visitor import ListVistor
from visitor.element import File, Directory, FileTreatmentException
def startMain():
try:
print("Making root entries")
rootdir = Directory("root")
bindir = Directory("bin")
tmpdir = Directory("tmp")
usrdir = Directory("usr")
rootdir.add(bindir)
rootdir.add(tmpdir)
rootdir.add(usrdir)
bindir.add(File("vi", 10000))
bindir.add(File("latex", 20000))
rootdir.accept(ListVistor())
print("")
print("Making user entries...")
yuki = Directory("yuki")
hanako = Directory("hanako")
tomura = Directory("tomura")
usrdir.add(yuki)
usrdir.add(hanako)
usrdir.add(tomura)
yuki.add(File("diary.html", 100))
yuki.add(File("composite.py", 200))
hanako.add(File("memo.tex", 300))
tomura.add(File("game.doc", 400))
tomura.add(File("junk.mail", 500))
rootdir.accept(ListVistor())
print("")
print("Occurring Exception...")
tmpfile = File("tmp.txt", 100)
bindir = Directory("bin")
tmpfile.add(bindir)
except FileTreatmentException as ex:
print(ex.message)
if __name__ == '__main__':
startMain()
Add an exception class
visitor/element.py
class FileTreatmentException(Exception):
def __init__(self,*args,**kwargs):
self.message = "FileTreatmentException"
-[Finishing "Introduction to Design Patterns Learned in Java Language" (Not)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java Introduction to Design Patterns Learned in Language-Finishing-Not-2cc9b34a30b2)
Recommended Posts