Problema

Agregar nuevas operaciones a una jerarquía de clases sin modificar esas clases.

Propósito

Separa las operaciones de los objetos sobre los que actúan. Puedes agregar nuevas operaciones creando nuevos visitors sin tocar las clases existentes.

Concepto clave

Operación externa: Como un inspector que visita diferentes tipos de edificios - cada edificio sabe cómo recibir al inspector, pero el inspector define qué inspeccionar.

Casos de uso comunes

  • Procesamiento de AST (Abstract Syntax Trees)
  • Sistemas de reporting sobre estructuras complejas
  • Operaciones sobre estructuras de documentos
  • Compiladores e intérpretes
  • Análisis de código fuente
  • Transformaciones de datos

¿Quién es quién en Visitor?

Actor Lo que realmente es Ejemplo Analogía
Visitor Interfaz que define visitElementA(), etc. DocumentVisitor - define qué hacer con cada elemento “Inspector” (interfaz)
ConcreteVisitor Implementa QUÉ hacer con cada elemento PDFExportVisitor, HTMLExportVisitor Inspector de seguridad, de impuestos
Element Interfaz de elementos con accept(visitor) DocumentElement - define cómo recibir visitantes “Edificio” (interfaz)
ConcreteElement Elementos que saben recibir visitantes Paragraph, Image, Table Casa, Oficina (saben recibir inspectores)

Diagrama

classDiagram
    namespace VisitorPattern {
        class Visitor {
            <<interface>>
            +visitConcreteElementA(element)
            +visitConcreteElementB(element)
        }
        
        class ConcreteVisitor1 {
            +visitConcreteElementA(element)
            +visitConcreteElementB(element)
        }
        
        class ConcreteVisitor2 {
            +visitConcreteElementA(element)
            +visitConcreteElementB(element)
        }
        
        class Element {
            <<interface>>
            +accept(visitor)
        }
        
        class ConcreteElementA {
            +accept(visitor)
            +operationA()
        }
        
        class ConcreteElementB {
            +accept(visitor)
            +operationB()
        }
        
        class ObjectStructure {
            -elements: List~Element~
            +accept(visitor)
        }
    }
    
    Visitor <|.. ConcreteVisitor1
    Visitor <|.. ConcreteVisitor2
    Element <|.. ConcreteElementA
    Element <|.. ConcreteElementB
    ObjectStructure --> Element

Ejemplo práctico

classDiagram
    namespace DocumentExample {
        class DocumentVisitor {
            <<interface>>
            +visitParagraph(paragraph)
            +visitImage(image)
            +visitTable(table)
            +visitHeader(header)
        }
        
        class WordExportVisitor {
            -wordDocument: WordDocument
            +visitParagraph(paragraph)
            +visitImage(image)
            +visitTable(table)
            +visitHeader(header)
        }
        
        class PDFExportVisitor {
            -pdfDocument: PDFDocument
            +visitParagraph(paragraph)
            +visitImage(image)
            +visitTable(table)
            +visitHeader(header)
        }
        
        class HTMLExportVisitor {
            -htmlBuilder: StringBuilder
            +visitParagraph(paragraph)
            +visitImage(image)
            +visitTable(table)
            +visitHeader(header)
        }
        
        class DocumentElement {
            <<interface>>
            +accept(visitor)
        }
        
        class Paragraph {
            -text: String
            +accept(visitor)
            +getText() String
        }
        
        class Image {
            -src: String
            -alt: String
            +accept(visitor)
            +getSrc() String
            +getAlt() String
        }
        
        class Table {
            -rows: List~TableRow~
            +accept(visitor)
            +getRows() List~TableRow~
        }
        
        class Header {
            -level: int
            -text: String
            +accept(visitor)
            +getLevel() int
            +getText() String
        }
        
        class Document {
            -elements: List~DocumentElement~
            +accept(visitor)
            +addElement(element)
        }
    }
    
    DocumentVisitor <|.. WordExportVisitor
    DocumentVisitor <|.. PDFExportVisitor
    DocumentVisitor <|.. HTMLExportVisitor
    DocumentElement <|.. Paragraph
    DocumentElement <|.. Image
    DocumentElement <|.. Table
    DocumentElement <|.. Header
    Document --> DocumentElement

Double Dispatch

sequenceDiagram
    participant Client
    participant Element
    participant Visitor
    
    Note over Client,Visitor: Double Dispatch Mechanism
    
    Client->>Element: accept(visitor)
    Note over Element: First dispatch based on element type
    Element->>Visitor: visitConcreteElement(this)
    Note over Visitor: Second dispatch based on visitor type
    Visitor->>Element: access element data
    Element-->>Visitor: element data
    Visitor->>Visitor: process element
    Visitor-->>Element: result
    Element-->>Client: operation complete

Flujo de procesamiento

flowchart TD
    A[Document] --> B[Accept Visitor]
    B --> C{For each element}
    C --> D[Paragraph]
    C --> E[Image]
    C --> F[Table]
    C --> G[Header]
    
    D --> H["paragraph.accept(visitor)"]
    E --> I["image.accept(visitor)"]
    F --> J["table.accept(visitor)"]
    G --> K["header.accept(visitor)"]
    
    H --> L["visitor.visitParagraph(this)"]
    I --> M["visitor.visitImage(this)"]
    J --> N["visitor.visitTable(this)"]
    K --> O["visitor.visitHeader(this)"]
    
    L --> P[Process & Export]
    M --> P
    N --> P
    O --> P

Ventajas

  • Extensibilidad: Fácil agregar nuevas operaciones sin modificar elementos
  • Separación: Separa algoritmos de la estructura de datos
  • Centralización: Operaciones relacionadas están en una clase visitor
  • Type Safety: Aprovecha el polimorfismo para type safety

Desventajas

  • Complejidad: Introduce muchas clases y métodos
  • Acoplamiento: Visitors conocen la estructura interna de elementos
  • Extensibilidad de elementos: Difícil agregar nuevos tipos de elementos
  • Circular dependencies: Puede crear dependencias circulares

Cuándo usar

  • Necesitas realizar muchas operaciones diferentes sobre una estructura compleja
  • La estructura de objetos es estable pero las operaciones cambian frecuentemente
  • Quieres evitar “contaminar” las clases de elementos con operaciones no relacionadas
  • Tienes una jerarquía de clases con operaciones que no encajan naturalmente

Cuándo NO usar

  • La estructura de objetos cambia frecuentemente
  • Solo tienes pocas operaciones simples
  • Las operaciones están fuertemente relacionadas con los elementos
  • Prefieres simplicidad sobre flexibilidad