Pular para o conteúdo principal

Visitor

Visão Geral

Visitor separa um algoritmo da estrutura de objetos sobre a qual ele opera, permitindo adicionar operações novas sem alterar as classes da estrutura.

É o padrão mais complexo do catálogo e o mais frequentemente aplicado onde não serve. A razão está num único eixo, e entendê-lo resolve quase toda decisão sobre ele.

Problema

Uma estrutura de objetos com vários tipos de nó precisa de várias operações diferentes.

Uma árvore sintática com números, operadores e variáveis precisa ser avaliada, impressa, otimizada, verificada quanto a tipos e serializada.

Colocar cada operação nos nós funciona — mas cada operação nova toca todas as classes de nó, e as classes acumulam responsabilidades sem relação entre si.

Visitor inverte: cada operação vira um objeto que sabe lidar com todos os tipos de nó.

Conceitos Centrais

O dilema da expressão

A formulação que decide tudo. Existem dois eixos de crescimento:

Tipos novos — mais tipos de nó na estrutura. Operações novas — mais coisas a fazer com a estrutura.

Nenhuma organização torna os dois baratos ao mesmo tempo.

Método no nóVisitor
Adicionar tipo de nóBarato — uma classeCaro — toca todos os visitantes
Adicionar operaçãoCaro — toca todos os nósBarato — uma classe

Visitor privilegia operações e penaliza tipos. Aplicá-lo a uma estrutura cujos tipos crescem é escolher o lado errado do dilema — e é o erro mais comum com este padrão.

Despacho duplo

O mecanismo. Em linguagens com despacho simples, o método chamado depende de um tipo. Visitor precisa de dois: o tipo do nó e o tipo da operação.

no.aceitar(visitante) ← despacha pelo tipo do nó
visitante.visitarNumero(this) ← despacha pelo tipo do visitante

O aceitar em cada nó existe só para isso. É cerimônia obrigatória, e o que torna o padrão verboso.

Em linguagens com correspondência de padrões sobre tipos algébricos, o mesmo se escreve sem hierarquia nem aceitar — o que explica por que Visitor é raro em código funcional.

O custo de legibilidade

Uma operação distribuída entre visitarNumero, visitarOperador, visitarVariavel fica coesa como classe e fragmentada como leitura: entender o que ela faz exige ler todos os métodos e reconstruir mentalmente o percurso.

Quando Usar

  • A estrutura de tipos é estável e as operações crescem.
  • As operações são complexas e não pertencem conceitualmente aos nós.
  • Uma operação precisa acumular estado ao percorrer a estrutura.
  • É necessário aplicar operações a uma hierarquia que você não controla.

Quando Não Usar

Quando tipos novos aparecem com frequência. O lado errado do dilema. Cada tipo toca todos os visitantes.

Quando há uma operação só. Toda a cerimônia sem o benefício.

Quando a linguagem tem correspondência de padrões. Uma função com match sobre os tipos faz o mesmo com uma fração do código.

Quando a operação pertence ao nó. Se avaliar é o que o nó é, ele deve saber avaliar-se.

Quando a legibilidade importa mais que a extensibilidade. Visitor fragmenta a leitura de cada operação, e em código lido com frequência isso pesa.

Alternativas

  • Correspondência de padrões — em linguagens que oferecem, é superior em quase tudo.
  • Método no nó — quando as operações são poucas e estáveis.
  • Iterator com condicional de tipo — menos elegante, muito mais legível, e adequado quando há poucos tipos.
  • Tabela de despacho — um mapa de tipo para função, sem hierarquia de visitantes.

Trade-offs

VisitorMétodo no nó
Operação nova é uma classeToca todos os nós
Tipo novo toca todos os visitantesÉ uma classe
Operação coesa numa classeDistribuída pelos nós
Leitura fragmentada por tipoCada nó legível isolado
aceitar obrigatório em cada nóSem cerimônia
Acumula estado no percursoEstado precisa ser passado

Modos de Falha

Tipo novo esquecido num visitante. Sem verificação exaustiva, o compilador não avisa e o comportamento fica errado silenciosamente.

Visitante com estado compartilhado. Reutilizar a instância entre percursos produz contaminação.

Hierarquia de visitantes. Um visitante base com comportamento padrão e subclasses que sobrescrevem: combina os custos de Visitor com os de herança.

Ordem de percurso implícita. Quem controla o percurso — o nó ou o visitante — não é óbvio, e mudar isso quebra visitantes que dependiam da ordem.

Erros Comuns

Aplicar a estrutura com tipos instáveis. O erro central.

Usar em linguagem com correspondência de padrões.

Colocar no visitante o que pertence ao nó.

Não tratar o caso do tipo desconhecido. Um visitante que não cobre um tipo deveria falhar explicitamente, não ignorar.

Onde ele aparece na prática

Compiladores e interpretadores. O caso canônico e o que melhor satisfaz a condição: a gramática de uma linguagem é estável por anos, e as operações — verificação de tipos, otimização, geração de código, formatação — crescem.

Ferramentas de análise estática. Cada regra é um visitante sobre a árvore sintática. Regras novas são frequentes; tipos de nó, não.

Serialização de estruturas complexas. Um visitante por formato.

Percurso de documentos estruturados. Transformações sobre árvores de documento.

Os quatro compartilham a mesma característica, e ela é o teste do padrão: a estrutura foi definida por uma especificação externa — uma gramática, um formato, um padrão — e por isso não muda no ritmo do código. Quando a estrutura é sua e evolui com o domínio, a condição não vale.

Exemplo Real

Um sistema de regras de negócio representava condições como árvore: comparações, conjunções, disjunções, negações, referências a campo.

Cinco tipos de nó, definidos pela gramática da linguagem de regras — estável desde o início.

As operações cresceram: avaliar, renderizar para leitura humana, converter em SQL, estimar custo, extrair os campos referenciados, validar.

Seis visitantes, cada um numa classe. Adicionar uma operação nunca tocou os nós.

O contraexemplo, no mesmo sistema: alguém aplicou Visitor à hierarquia de documentos do domínio — contrato, apólice, endosso, sinistro. Essa hierarquia crescia: em dois anos ganhou cinco tipos novos.

Cada tipo novo tocou os quatro visitantes existentes. Em duas ocasiões um tipo foi adicionado e um visitante não foi atualizado — e o defeito passou porque nada obrigava a exaustividade.

Foi revertido para métodos nos próprios documentos.

A mesma equipe, o mesmo padrão, dois resultados opostos. A diferença foi qual eixo crescia.

Conceitos Relacionados

  • Composite — a estrutura sobre a qual Visitor costuma operar.
  • Iterator — percurso sem distinção de tipo.
  • Strategy — variação de algoritmo sem estrutura.

Exercício Prático

Escolha uma hierarquia de tipos do seu sistema e responda com o histórico: quantos tipos novos foram adicionados no último ano? Quantas operações novas?

Se tipos > operações, Visitor é o padrão errado ali. Se operações > tipos e a estrutura vem de uma especificação externa, pode valer.

Perguntas de Entrevista

  • O que é o dilema da expressão e qual lado Visitor escolhe?
  • Por que Visitor precisa de despacho duplo?
  • Em que tipo de linguagem ele deixa de ser necessário?

Para Aprofundar

  • Gamma, Erich et al. Design Patterns. Addison-Wesley, 1994.
  • Wadler, Philip. The Expression Problem, 1998.