Saltar al contenido

Visitor

Un patrón de comportamiento para agregar operaciones nuevas a una jerarquía de clases, sin tocar las clases sobre las que operan.

6 min. de lectura

Visitor es un patrón de comportamiento que permite separar un algoritmo de los objetos sobre los que opera, para poder agregar operaciones nuevas sin modificar esas clases.

El problema

El catálogo de AndesShop tiene varios tipos de ítems — PhysicalProduct, DigitalGiftCard, Bundle (un combo de varios productos) — y con el tiempo necesita aplicarles operaciones que no tienen nada que ver entre sí: calcular el peso total para el envío, calcular el impuesto correspondiente, generar un XML para exportar a un marketplace externo. Agregar cada operación nueva directamente como un método en PhysicalProduct, DigitalGiftCard y Bundle va llenando esas clases de métodos que no tienen relación con lo que un producto es, solo con las distintas cosas que alguien quiere hacer con él.

La solución

Visitor mueve cada operación a una clase visitante aparte, con un método distinto para cada tipo concreto de ítem. Los ítems del catálogo solo necesitan un método, accept(Visitor visitor), que le pasa el control al visitante — es el visitante quien decide, según el tipo real del ítem, qué hacer con él. Agregar una operación nueva (por ejemplo, exportar a un nuevo marketplace) es crear un visitante nuevo, sin tocar ninguna clase del catálogo.

Estructura de Visitor: una operación nueva es un visitor nuevo. Agregar TaxVisitor no toca ninguna clase del catálogo.

Ejemplo en Java

interface CatalogItem {
    void accept(CatalogVisitor visitor);
}

class PhysicalProduct implements CatalogItem {
    double weightKg;
    public void accept(CatalogVisitor visitor) { visitor.visit(this); }
}

class DigitalGiftCard implements CatalogItem {
    public void accept(CatalogVisitor visitor) { visitor.visit(this); }
}

interface CatalogVisitor {
    void visit(PhysicalProduct product);
    void visit(DigitalGiftCard giftCard);
}

// Un visitante concreto: calcula el peso total a despachar
class ShippingWeightVisitor implements CatalogVisitor {
    private double totalWeight = 0;

    public void visit(PhysicalProduct product) {
        totalWeight += product.weightKg;
    }

    public void visit(DigitalGiftCard giftCard) {
        // no pesa nada — pero el visitante decide eso, no la clase GiftCard
    }

    public double getTotalWeight() {
        return totalWeight;
    }
}
ShippingWeightVisitor weightVisitor = new ShippingWeightVisitor();
for (CatalogItem item : cart.getItems()) {
    item.accept(weightVisitor); // cada ítem delega en el visitante según su propio tipo
}
System.out.println(weightVisitor.getTotalWeight());

Cuándo usarlo

  • Cuando necesitás realizar operaciones distintas y no relacionadas entre sí sobre una jerarquía de clases ya definida, sin ensuciar esas clases con métodos ajenos a su responsabilidad.
  • Cuando la jerarquía de clases sobre la que operás es relativamente estable, pero las operaciones que se le aplican siguen creciendo.

Cuándo evitarlo

Si la jerarquía de clases cambia con frecuencia (se agregan tipos nuevos seguido), Visitor se vuelve costoso de mantener: cada tipo nuevo obliga a agregar un método visit() en todas las clases visitantes existentes.

Ventajas y desventajas

VentajasDesventajas
Permite agregar operaciones nuevas sin modificar las clases sobre las que operanAgregar un tipo nuevo a la jerarquía obliga a actualizar todos los visitantes existentes
Agrupa en un solo lugar el código de una misma operación, en vez de dispersarlo en cada clasePuede romper el encapsulamiento si el visitante necesita acceder a demasiado estado interno de cada tipo

Relación con otros patrones

  • Se combina naturalmente con Composite para aplicar operaciones sobre toda una estructura de árbol.
  • Iterator se ocupa de recorrer una colección; Visitor se ocupa de qué hacer con cada elemento durante ese recorrido — suelen aparecer juntos.

Te sirvió, compartilo

// ¿te sirvió?
// compartilo