Visitor

The Visitor represents an operation to be performed on the elements of an object without changing the classes of the elements on which it operates. That is to say, it allows you to separate algorithms from the objects on which they operate by giving access to a client to the components of the class itself.

TL;DR

The Visitor pattern allows you add additional operations to an object without having to modify that object (beyond having a way of accepting the visitor).

Problem

Suppose you have a file system with different file types, such as TextFile, ImageFile, AudioFile. As you create this file system, you realize that you might need to perform certain operations on the files, but you don’t know which operations you might need beforehand. Rather than modify the file classes every time you need an operation, you need a way to access the internal data of a file class from the outside.

Solution

The solution is to design the File base class so that it includes an accept() method, which itself takes a visitor object. This is done in order to give the Visitor class access to the internal data of the class in question.

Additionally, the Visitor class implements methods that are unique for each type of file, and then each concrete FileType knows to call the correct method for its own type (since we don’t necessarily know the type that will be calling the visitor beforehand).

from abc import ABC, abstractmethod
 
# --- Visitor Interface ---
class FileVisitor(ABC):
    @abstractmethod
    def visit_text_file(self, file):
        pass
 
    @abstractmethod
    def visit_image_file(self, file):
        pass
 
    @abstractmethod
    def visit_audio_file(self, file):
        pass
 
# --- File Interface (Element) ---
class File(ABC):
    def __init__(self, name):
        self.name = name
 
    @abstractmethod
    def accept(self, visitor: FileVisitor):
        pass
 
# --- Concrete File Types ---
class TextFile(File):
    def accept(self, visitor: FileVisitor):
        visitor.visit_text_file(self)
 
class ImageFile(File):
    def accept(self, visitor: FileVisitor):
        visitor.visit_image_file(self)
 
class AudioFile(File):
    def accept(self, visitor: FileVisitor):
        visitor.visit_audio_file(self)
 
# --- Concrete Visitors (Operations) ---
class VirusScanner(FileVisitor):
    def visit_text_file(self, file):
        print(f"Scanning text file '{file.name}' for viruses... Clean.")
 
    def visit_image_file(self, file):
        print(f"Scanning image file '{file.name}' for viruses... Clean.")
 
    def visit_audio_file(self, file):
        print(f"Scanning audio file '{file.name}' for viruses... Clean.")
 
class PreviewGenerator(FileVisitor):
    def visit_text_file(self, file):
        print(f"Previewing text file '{file.name}': First few lines...")
 
    def visit_image_file(self, file):
        print(f"Generating thumbnail for image '{file.name}'.")
 
    def visit_audio_file(self, file):
        print(f"Generating audio snippet for '{file.name}'.")
 
# --- Usage ---
files = [
    TextFile("report.txt"),
    ImageFile("photo.jpg"),
    AudioFile("song.mp3")
]
 
scanner = VirusScanner()
previewer = PreviewGenerator()
 
print("Virus Scan:")
for file in files:
    file.accept(scanner)
 
print("\nPreview Generation:")
for file in files:
    file.accept(previewer)