🧳 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.

📸 1. Un ejemplo sencillo: El Turista
Imagina una ciudad con edificios a los que llamaremos elementos:
- Museo, parque, tienda.
- Llega un Turista (Visitor). Visita el Museo (hace fotos), el parque (descansa) y la tienda (compra).
- Llega un inspector de sanidad (otro Visitor). Visita el museo (revisa baños), el parque (revisa papeleras) y la tienda (revisa cocina).
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 ( |
Turista / Inspector | Visitor Concreto | Define qué hacer con cada tipo de edificio. |
🛠️ 2. Los tres pilares del patrón Visitor
1. Interfaz Visitor
- Declara métodos de visita para cada tipo de elemento concreto:
visitarMuseo(),visitarParque().
2. Interfaz Elemento
- Declara el método
aceptar(Visitor v).
3. Double Dispatch
- Es la magia del patrón. El elemento llama a
v->visitarEsteElemento($this). Así, se elige el método correcto basándose en el tipo del visitante Y el tipo del elemento.
🤔 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?
- Open/Closed Principle: Añades nuevas operaciones (Visitors) sin tocar las clases de los elementos.
- 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
- Difícil de extender la jerarquía: Como vimos en el test, si añades un nuevo tipo de Elemento, te toca refactorizar todos los visitantes. Úsalo solo si tus clases de elementos cambian poco.
🤔 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! 🍻