🚨 ¡Nueva review! 🔇 Los mejores cascos con ANC del mercado: los Sony WH-1000XM4 . ¡Échale un ojo! 👀

El Patrón de diseño Visitor

Añade funcionalidades a tus clases sin tocarlas.

Escrito por domin el 7 de diciembre de 2025

🧳 El Patrón Visitor: El Turista Educado

El Patrón Visitor (Visitante), te permite añadir nuevas operaciones a una estructura de objetos existente sin modificar esas clases.

Imagina que tienes una jerarquía de clases estable, por ejemplo unas formas geométricas, y quieres añadir una nueva operación (ej: exportar a XML), pero no quieres ensuciar todas las clases con ese método nuevo, o no tienes acceso al código fuente.

La respuesta es crear una clase externa Visitor que contiene el algoritmo y sabe cómo ejecutarlo para cada tipo de objeto concreto.

Diagrama del Patrón Visitor.

📸 1. Un ejemplo sencillo: El Turista

Imagina una ciudad con edificios a los que llamaremos elementos:

Los edificios son los mismos, no cambian. Pero lo que se hace en ellos depende de quién los visita.

Concepto (Analogía)

Rol en Visitor

Tarea Principal

Edificios (Museo, Parque)

Elementos Concretos

Aceptan al visitante (aceptar(v)).

Turista / Inspector

Visitor Concreto

Define qué hacer con cada tipo de edificio.

🛠️ 2. Los tres pilares del patrón Visitor

1. Interfaz Visitor

2. Interfaz Elemento

3. Double Dispatch

🤔 1. ¿Qué es el Double Dispatch?

📄 3. El ejemplo clásico: Exportación

Tienes clases Circulo, Cuadrado, Triangulo. Quieres exportarlas a XML y luego a JSON. En lugar de añadir toXML() y toJSON() a cada figura, creas ExportarXMLVisitor y ExportarJSONVisitor.

🤔 2. ¿Qué pasa si añades una nueva clase de Elemento (ej: Rectángulo)?

✅ 4. ¿Por qué usarlo?

  1. Open/Closed Principle: Añades nuevas operaciones (Visitors) sin tocar las clases de los elementos.
  2. Single Responsibility Principle: Agrupas toda la lógica de una operación (ej: exportar) en una sola clase, en lugar de tenerla esparcida.

❌ 5. Desventaja a considerar

🤔 3. ¿Es necesario que el Visitor tenga acceso a los datos privados del Elemento?

💡 6. Conclusión

El Visitor es potente pero tiene un coste de mantenimiento si tu jerarquía de datos cambia mucho. Es ideal para compiladores, exportadores o validadores sobre estructuras estables.

🧠 7. Ejemplo Práctico en PHP

Vamos a exportar formas geométricas.

<?php

// 🔹 1. Interfaces (Sin cambios)
interface Visitor {
    public function visitarCirculo(Circulo $c): void;
    public function visitarCuadrado(Cuadrado $c): void;
}

interface Forma {
    public function aceptar(Visitor $v): void;
}

// 🔹 2. Elementos Concretos (Sin cambios)
class Circulo implements Forma {
    public int $radio;
    public function __construct(int $radio) { $this->radio = $radio; }

    public function aceptar(Visitor $v): void {
        $v->visitarCirculo($this);
    }
}

class Cuadrado implements Forma {
    public int $lado;
    public function __construct(int $lado) { $this->lado = $lado; }

    public function aceptar(Visitor $v): void {
        $v->visitarCuadrado($this);
    }
}

// 🔹 3. Visitors Concretos (¡Aquí está la mejora!)
class ExportarXMLVisitor implements Visitor {
    // Estado interno para acumular el resultado
    private string $buffer = "";

    public function visitarCirculo(Circulo $c): void {
        $this->buffer .= "    <circulo radio=\"{$c->radio}\" />\n";
    }

    public function visitarCuadrado(Cuadrado $c): void {
        $this->buffer .= "    <cuadrado lado=\"{$c->lado}\" />\n";
    }

    // Método para obtener el resultado final
    public function obtenerXML(): string {
        return "<formas>\n" . $this->buffer . "</formas>";
    }
}

class ExportarJSONVisitor implements Visitor {
    // Usamos un array, es más limpio que concatenar strings para JSON
    private array $data = [];

    public function visitarCirculo(Circulo $c): void {
        $this->data[] = [
            'tipo' => 'circulo',
            'radio' => $c->radio
        ];
    }

    public function visitarCuadrado(Cuadrado $c): void {
        $this->data[] = [
            'tipo' => 'cuadrado',
            'lado' => $c->lado
        ];
    }

    public function obtenerJSON(): string {
        // JSON_PRETTY_PRINT para que se vea bonito en el ejemplo
        return json_encode($this->data, JSON_PRETTY_PRINT);
    }
}

// 🔹 4. Uso (Cliente)
$formas = [
    new Circulo(5),
    new Cuadrado(10),
    new Circulo(2)
];

// Instanciamos los visitantes
$exportadorXML = new ExportarXMLVisitor();
$exportadorJSON = new ExportarJSONVisitor();

// Recorremos la estructura una sola vez para cada visitante
foreach ($formas as $f) {
    $f->aceptar($exportadorXML);
    $f->aceptar($exportadorJSON);
}

// 🔹 AHORA decidimos qué hacer con los datos (Salida)

echo "--- Archivo XML Generado ---\n";
echo $exportadorXML->obtenerXML();

echo "\n\n--- Archivo JSON Generado ---\n";
echo $exportadorJSON->obtenerJSON();

// Opcional: Podrías guardar esto en un archivo real fácilmente:
// file_put_contents('exportacion.json', $exportadorJSON->obtenerJSON());

/* 🖥️ SALIDA REAL:

--- Archivo XML Generado ---
<formas>
    <circulo radio="5" />
    <cuadrado lado="10" />
    <circulo radio="2" />
</formas>

--- Archivo JSON Generado ---
[
    {
        "tipo": "circulo",
        "radio": 5
    },
    {
        "tipo": "cuadrado",
        "lado": 10
    },
    {
        "tipo": "circulo",
        "radio": 2
    }
]
*/

EA, ¡saluditos y nos vemos en los bares! 🍻