Learn the design pattern "Visitor" in Python

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.

■ Visitor pattern (visitor pattern)

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 W3sDesign_Visitor_Design_Pattern_UML.jpg

UML class diagram Visitor_design_pattern.svg.png

□ Memorandum

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.

■ "Visitor" sample program

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

■ Details of sample program

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

(1) The role of a visitor

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

(2) The role of Concrete Visitor

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

(3) Role of Element

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

(4) Role of ConcreteElement

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)

(5) The role of Object Structure

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.

(6) The role of Client

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()

(7) Other

Add an exception class

visitor/element.py


class FileTreatmentException(Exception):
    def __init__(self,*args,**kwargs):
        self.message = "FileTreatmentException"

■ Reference URL

-[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

Learn the design pattern "Visitor" in Python
Learn the design pattern "Prototype" in Python
Learn the design pattern "Builder" in Python
Learn the design pattern "Flyweight" in Python
Learn the design pattern "Observer" in Python
Learn the design pattern "Memento" in Python
Learn the design pattern "Proxy" in Python
Learn the design pattern "Command" in Python
Learn the design pattern "Bridge" in Python
Learn the design pattern "Mediator" in Python
Learn the design pattern "Decorator" in Python
Learn the design pattern "Iterator" in Python
Learn the design pattern "Strategy" in Python
Learn the design pattern "Composite" in Python
Learn the design pattern "State" in Python
Learn the design pattern "Adapter" in Python
Learn the design pattern "Abstract Factory" in Python
Learn the design pattern "Template Method" in Python
Learn the design pattern "Factory Method" in Python
Learn the design pattern "Chain of Responsibility" in Python
Visitor pattern in Python
Learn the design pattern "Singleton" with Python
Learn the design pattern "Facade" with Python
Implement the Singleton pattern in Python
Singleton pattern in Python
About the Visitor pattern
Download the file in Python
Find the difference in Python
Learn cumulative sum in Python
Design Patterns in Python: Introduction
Learn exploration in Python # 1 Full exploration
Python Design Pattern --Template method
Python in the browser: Brython's recommendation
Save the binary file in Python
Hit the Sesami API in Python
Get the desktop path in Python
Get the script path in Python
In the python command python points to python3.8
Hit the web API in Python
I wrote the queue in Python
Calculate the previous month in Python
Examine the object's class in python
Get the desktop path in Python
Get the host name in Python
Access the Twitter API in Python
The first step in Python Matplotlib
I wrote the stack in Python
Master the weakref module in Python
Learn the basics of Python ① Beginners
Try using the Wunderlist API in Python
Check the behavior of destructor in Python
[Python Kivy] About changing the design theme
[Gang of Four] Design pattern learning --Visitor
Try using the Kraken API in Python
Learn the basics while touching python Variables
Write the test in a python docstring
OR the List in Python (zip function)
GoF design pattern from the problem 2. Structure
Display Python 3 in the browser with MAMP
Tweet using the Twitter API in Python
Check if the URL exists in Python