GoF, p. 331
Visitante
Visitor
Adicione operações novas a uma hierarquia de classes sem alterá-las.
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.
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.
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.
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.
- Classes de domínio com métodos
paraXML(),paraCSV(),paraRelatorio()misturados à regra de negócio. instanceofem cascata para decidir o que fazer com cada tipo.
Estrutura
- 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
// ── 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. - javax.lang.model.element.AnnotationValueVisitor (Java Annotation Processing)
- AST visitors de compiladores e linters (Babel, ESLint, TypeScript)
- java.nio.file.FileVisitor
Consequências
- 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).
- 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.
- 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.
- 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
- 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ó.
- Command
- Ambos reificam comportamento; Visitor opera sobre uma hierarquia de tipos.
Verificação
Qual é o trade-off fundamental do Visitor?
- Adicionar OPERAÇÕES fica fácil; adicionar novos TIPOS de elemento fica caro (muda todos os visitantes)
- Ele economiza memória mas gasta CPU
- Ele só funciona em linguagens com sobrecarga de métodos
- 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.