Pular para o conteúdo
GoF Padrões de Projeto Comportamentais
Comportamental escopo de objeto
GoF, p. 331

Visitante

Visitor

Adicione operações novas a uma hierarquia de classes sem alterá-las.

Complexidade alta Frequência de uso rara
Intençãocomo o livro define

Representar uma operação a ser executada sobre os elementos de uma estrutura de objetos. Visitor permite definir uma nova operação sem mudar as classes dos elementos sobre os quais opera.

Analogiauma imagem do mundo real

O médico que visita a casa

Um médico faz visitas domiciliares. Ele age de um jeito com o bebê, de outro com o adulto e de outro com o idoso — mas nenhum morador precisa aprender medicina. Amanhã pode aparecer um eletricista, que trata cada cômodo do seu jeito; a casa continua igual.

O problema

Você precisa executar operações novas e não relacionadas ao domínio (exportar para XML, calcular métricas, gerar relatórios) sobre uma hierarquia de classes já estável. Enfiar esses métodos em cada classe as polui e mistura responsabilidades.

A solução

Mova o comportamento para uma classe Visitor separada, com um método por tipo de elemento. Cada elemento ganha um único método `aceitar(visitante)` que chama de volta o método correto — o *double dispatch*, que faz a escolha do método depender de dois tipos ao mesmo tempo.

Sintomascomo reconhecer no seu código
  • Classes de domínio com métodos paraXML(), paraCSV(), paraRelatorio() misturados à regra de negócio.
  • instanceof em cascata para decidir o que fazer com cada tipo.

Estrutura

aceitar → visitarClient«interface»Forma+ aceitar(v)CirculoRetangulo«interface»Visitante+ visitarCirculo(c)+ visitarRetangulo(r)ExportadorXMLCalculadoraArea
Fig. 23 — Visitor herda / implementa cria usa / contém
Ver este diagrama sendo desenhado, traço a traço
Participantes
Visitor
Declara `visitarX()` para cada classe concreta de elemento.
ConcreteVisitor
Implementa uma operação inteira sobre a estrutura.
Element
Declara `aceitar(visitante)`.
ConcreteElement
Implementa `aceitar` chamando o método correspondente do visitante.
ObjectStructure
Enumera os elementos e aplica o visitante a todos.

Implementação

Listagem 23 Duas operações novas sobre uma hierarquia que não muda
// ── Elementos: estáveis. Ganham UM método e nunca mais mudam. ──
class Forma { aceitar(v) { throw new Error('abstrato'); } }

class Circulo extends Forma {
  constructor(r) { super(); this.raio = r; }
  aceitar(v) { return v.visitarCirculo(this); }        // ← double dispatch
}
class Retangulo extends Forma {
  constructor(l, a) { super(); this.largura = l; this.altura = a; }
  aceitar(v) { return v.visitarRetangulo(this); }
}
class Grupo extends Forma {
  constructor(...filhos) { super(); this.filhos = filhos; }
  aceitar(v) { return v.visitarGrupo(this); }
}

// ── Visitante 1: exportar para SVG ────────────────────
class ExportadorSVG {
  visitarCirculo(c)   { return `<circle r="${c.raio}"/>`; }
  visitarRetangulo(r) { return `<rect width="${r.largura}" height="${r.altura}"/>`; }
  visitarGrupo(g)     { return `<g>${g.filhos.map(f => f.aceitar(this)).join('')}</g>`; }
}

// ── Visitante 2: calcular área — SEM tocar nas Formas ─
class CalculadoraDeArea {
  visitarCirculo(c)   { return Math.PI * c.raio ** 2; }
  visitarRetangulo(r) { return r.largura * r.altura; }
  visitarGrupo(g)     { return g.filhos.reduce((s, f) => s + f.aceitar(this), 0); }
}

// ── Visitante 3: contar elementos — idem ──────────────
class ContadorDeFormas {
  visitarCirculo()  { return 1; }
  visitarRetangulo(){ return 1; }
  visitarGrupo(g)   { return g.filhos.reduce((s, f) => s + f.aceitar(this), 0); }
}

const desenho = new Grupo(new Circulo(10), new Retangulo(4, 6), new Grupo(new Circulo(2)));

console.log(desenho.aceitar(new ExportadorSVG()));
console.log(desenho.aceitar(new CalculadoraDeArea()).toFixed(2));   // 353.13
console.log(desenho.aceitar(new ContadorDeFormas()));               // 3

// ⚖️ O trade-off: adicionar uma OPERAÇÃO é trivial (nova classe).
//    Adicionar um ELEMENTO obriga a mexer em TODOS os visitantes.
Na práticaonde ele já existe
  • javax.lang.model.element.AnnotationValueVisitor (Java Annotation Processing)
  • AST visitors de compiladores e linters (Babel, ESLint, TypeScript)
  • java.nio.file.FileVisitor

Consequências

A favor
  • Adiciona operações novas sem alterar as classes existentes (Aberto/Fechado).
  • Agrupa numa só classe operações relacionadas que estariam espalhadas.
  • Pode acumular estado enquanto percorre a estrutura (ex.: totais, índices).
Contra
  • Toda vez que um elemento novo entra na hierarquia, TODOS os visitantes precisam ser atualizados.
  • Visitantes podem não ter acesso aos campos privados dos elementos.
  • É o padrão mais difícil de entender e de justificar do catálogo.
Use quando
  • A hierarquia de elementos é estável, mas as operações sobre ela mudam com frequência.
  • Você quer limpar a lógica de negócio auxiliar de dentro das classes de domínio.
  • Um comportamento só faz sentido para algumas classes da hierarquia.
Evite quando
  • A hierarquia de elementos muda com frequência — a manutenção vira um pesadelo.
  • A linguagem tem pattern matching sobre tipos (uma alternativa muito mais simples).

Relações com outros padrões

costuma andar junto
Composite
A dupla clássica: Visitor executa operações sobre toda a árvore Composite.
Interpreter
Visitor aplica análises e otimizações sobre a árvore sintática.
Iterator
Iterator percorre estruturas complexas; Visitor age em cada nó.
parecido / fácil de confundir
Command
Ambos reificam comportamento; Visitor opera sobre uma hierarquia de tipos.

Verificação

Questãoa resposta está marcada

Qual é o trade-off fundamental do Visitor?

  1. Adicionar OPERAÇÕES fica fácil; adicionar novos TIPOS de elemento fica caro (muda todos os visitantes)
  2. Ele economiza memória mas gasta CPU
  3. Ele só funciona em linguagens com sobrecarga de métodos
  4. Ele elimina a necessidade de polimorfismo

É o “problema da expressão”: OO facilita adicionar tipos e dificulta adicionar operações; Visitor inverte esse eixo. Escolha conforme o que muda mais no seu sistema.