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
Visitor
pattern ** 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, the
Directoryclass serves this role. (Two roles per person) The
Directory 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