knowledge/technology/programming/patterns/behavioral/Visitor Pattern.md
2023-12-04 11:02:23 +01:00

4.2 KiB

obj
concept

Visitor Pattern

Description:

The Visitor Pattern is a behavioral design pattern that allows you to add new operations to a set of related classes without modifying their code. It separates the algorithm (or operation) from the object structure, enabling you to define new behaviors (visitors) independently and apply them to existing objects. This pattern is useful when you have a stable set of classes but need to frequently introduce new operations on those classes.

How it's Used:

  1. Element Interface: Define an element interface that declares an accept method which accepts a visitor.
  2. Concrete Elements: Create concrete element classes that implement the element interface. These are the objects that will be visited by visitors.
  3. Visitor Interface: Define a visitor interface that declares visit methods for each concrete element type. The visitor interface should have one visit method for each type of element it can visit.
  4. Concrete Visitors: Create concrete visitor classes that implement the visitor interface. Each concrete visitor provides implementations for the visit methods to perform specific operations on the elements.
  5. Element Structure: Create a structure (e.g., a collection) of concrete elements that need to be visited.
  6. Accept Visitor: In the concrete elements, implement the accept method to call the appropriate visit method on the visitor.
  7. Client Code: In client code, create instances of concrete elements and visitors, and apply visitors to elements as needed.

Example:

Let's create an example of the Visitor Pattern in Python to model a document structure with elements (e.g., paragraphs and tables) and a visitor that counts the elements.

from abc import ABC, abstractmethod

# Step 1: Element Interface
class Element(ABC):
    @abstractmethod
    def accept(self, visitor):
        pass

# Step 2: Concrete Elements
class Paragraph(Element):
    def accept(self, visitor):
        visitor.visit_paragraph(self)

class Table(Element):
    def accept(self, visitor):
        visitor.visit_table(self)

# Step 3: Visitor Interface
class Visitor(ABC):
    @abstractmethod
    def visit_paragraph(self, paragraph):
        pass

    @abstractmethod
    def visit_table(self, table):
        pass

# Step 4: Concrete Visitors
class ElementCounter(Visitor):
    def __init__(self):
        self.paragraph_count = 0
        self.table_count = 0

    def visit_paragraph(self, paragraph):
        self.paragraph_count += 1

    def visit_table(self, table):
        self.table_count += 1

# Step 5: Element Structure
class Document:
    def __init__(self, elements):
        self.elements = elements

    def accept(self, visitor):
        for element in self.elements:
            element.accept(visitor)

# Step 7: Client Code
def main():
    paragraph1 = Paragraph()
    paragraph2 = Paragraph()
    table1 = Table()
    table2 = Table()

    elements = [paragraph1, table1, paragraph2, table2]

    document = Document(elements)
    counter = ElementCounter()

    document.accept(counter)

    print(f"Paragraph Count: {counter.paragraph_count}")
    print(f"Table Count: {counter.table_count}")

if __name__ == "__main__":
    main()

In this Python example, we define an Element interface (Step 1) with an accept method. We create two concrete element classes (Paragraph and Table) that implement this interface (Step 2).

We define a Visitor interface (Step 3) with visit_paragraph and visit_table methods. We then create a concrete visitor class (ElementCounter) that implements the visitor interface (Step 4) and counts the elements it visits.

The Document class (Step 5) contains a collection of elements, and it implements the accept method to iterate through the elements and call their accept method to let the visitor visit them.

In the main function (Step 7), we create instances of elements and visitors, apply the visitor to the document, and display the counts of paragraphs and tables visited. This separation allows us to add new visitors to perform different operations on the document structure without modifying the element classes.