ComportamentalAvançado7 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Interpreter

Representa as regras de uma mini-linguagem como uma árvore de objetos, com uma classe por regra gramatical, e avalia frases dessa linguagem percorrendo a árvore.

Em 10 segundos

Cada regra da mini-linguagem vira uma classe, e uma frase vira uma árvore de objetos. Avaliar é chamar `interpretar` na raiz — o que permite guardar regras como dado, validá-las e explicá-las, sem `eval` e sem deploy a cada ajuste.

  • #gramatica
  • #dsl
  • #regras
  • #gof
Analogia

Blocos de montar com encaixe fixo

Numa caixa de blocos, poucas peças com encaixes compatíveis produzem infinitas construções. Você não precisa de uma peça nova para cada modelo — precisa das peças certas e das regras de encaixe. Uma expressão funciona assim: E, OU e NÃO são poucos tipos de peça, mas combinados descrevem qualquer condição que a linguagem permita.

01Conceito

O problema

Regras de negócio voláteis são um mau encaixe para código compilado. Quem decide 'clientes pro da região sul não bloqueados' é a área de negócio, e a espera pelo próximo deploy custa oportunidade.

As saídas fáceis são ruins: eval sobre texto do usuário é execução remota de código, e uma tabela de flags rígida não expressa combinações com parênteses e negação.

O Interpreter é, estruturalmente, um Composite especializado: terminais são folhas, não-terminais são compostos, e a avaliação é a travessia recursiva. Se você entendeu Composite, já entendeu metade daqui — o que muda é a intenção, que passa a ser representar uma gramática.

É importante separar duas coisas que costumam se confundir: parsing e interpretação. O padrão trata só da segunda — dada a árvore, como avaliá-la. Transformar o texto plano = pro E regiao = sul em objetos é trabalho de um parser, que o GoF deixa de fora e que na prática costuma ser a parte mais trabalhosa.

Também vale notar a alternativa quase sempre melhor para gramáticas maiores: separar a árvore da avaliação e usar Visitor. Assim a mesma árvore serve para avaliar, validar, otimizar e imprimir de volta como texto — em vez de acumular tudo dentro de cada classe de nó.

02Conceito

O limite prático do padrão

O Interpreter é o padrão menos usado do GoF, e por um motivo honesto: ele escala mal. Cada regra gramatical é uma classe, e gramáticas reais têm dezenas.

Com aritmética, precedência de operadores, funções, variáveis e tipos, a contagem de classes explode e as interações entre elas ficam difíceis de manter corretas. Nesse território, geradores de parser ou bibliotecas de expressão prontas ganham disparado.

A faixa em que ele brilha é estreita e real: gramáticas de cinco a dez regras, estáveis, com necessidade de auditar e explicar. Filtros booleanos, condições de alerta e critérios de segmentação cabem bem.

Antes de escrever o seu, considere uma linguagem de expressão pronta (CEL, JsonLogic, expressões de regra de mercado). Elas já resolveram parsing, segurança e desempenho — e o valor que você agrega raramente está em ter inventado a sintaxe.

Como se organiza
  • Enão-terminal — exige os dois lados

    • plano = proterminal — compara e devolve

    • OUnão-terminal — basta um lado

      • regiao = sulterminal

      • NÃO (bloqueado = sim)não-terminal de um filho só

A frase inteira é esta árvore. Avaliar é chamar interpretar() na raiz e deixar a recursão descer até os terminais, que são os únicos que realmente olham os dados.
Sem o padrão × com o padrão

Regra como árvore de objetos

  • a regra é dado: pode ser salva, versionada e revisada
  • alterar não exige deploy
  • sem eval — só os operadores que você definiu existem
  • dá para instrumentar e explicar cada decisão

O custo é uma classe por regra gramatical e a necessidade de um parser se a entrada for texto — além de avaliação bem mais lenta que código compilado.

A troca é expressividade controlada por desempenho e cerimônia. Faz sentido enquanto a gramática for pequena; quando cresce, ferramentas dedicadas passam a ganhar.
03Anatomia

Navegue pelas camadas

Camada 1/3Expressão terminal

Camada 1

Expressão terminal

a folha que olha os dados

Implementa a regra mais simples da gramática — uma comparação, uma constante. É o único tipo que realmente consulta o contexto; todo o resto apenas combina resultados.

No código

class Campo { interpretar(ctx) { return ctx[this.nome] === this.valor } }

Se violar: terminal que consulta banco ou chama API transforma a avaliação da árvore numa cascata de I/O imprevisível.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

type Contexto = Record<string, string>;

interface Expressao {
  interpretar(ctx: Contexto): boolean;
}

// Terminal: a regra mais simples da gramatica
class Campo implements Expressao {
  constructor(
    private nome: string,
    private valor: string,
  ) {}
  interpretar(ctx: Contexto): boolean {
    return ctx[this.nome] === this.valor;
  }
}

// Nao-terminais: regras compostas por outras regras
class E implements Expressao {
  constructor(private esq: Expressao, private dir: Expressao) {}
  interpretar(ctx: Contexto): boolean {
    return this.esq.interpretar(ctx) && this.dir.interpretar(ctx);
  }
}

class Ou implements Expressao {
  constructor(private esq: Expressao, private dir: Expressao) {}
  interpretar(ctx: Contexto): boolean {
    return this.esq.interpretar(ctx) || this.dir.interpretar(ctx);
  }
}

class Nao implements Expressao {
  constructor(private dentro: Expressao) {}
  interpretar(ctx: Contexto): boolean {
    return !this.dentro.interpretar(ctx);
  }
}

// "plano = pro E (regiao = sul OU NAO(bloqueado = sim))"
const regra = new E(
  new Campo("plano", "pro"),
  new Ou(new Campo("regiao", "sul"), new Nao(new Campo("bloqueado", "sim"))),
);

console.log(regra.interpretar({ plano: "pro", regiao: "norte", bloqueado: "nao" })); // true
06No mundo real

Casos de uso

Segmentação de clientes em ferramenta de marketing

Cenário

Analistas precisam montar públicos como 'plano pro E (região sul OU compra nos últimos 30 dias)' pela interface, sem pedir nada ao time de engenharia.

Como o padrão entra

A interface monta a árvore de expressões; o backend a avalia contra cada cliente. A regra é salva como JSON, versionada e reutilizável entre campanhas.

Trade-off: Avaliar a árvore por cliente não escala para milhões de registros: em algum momento a mesma árvore precisa ser traduzida para SQL, e manter os dois avaliadores coerentes vira trabalho permanente.

Condições de alerta em monitoramento

Cenário

Times definem alertas como 'erro acima de 5% E latência acima de 300ms por 5 minutos', e cada equipe tem critérios próprios.

Como o padrão entra

Cada condição é uma árvore avaliada a cada janela de métricas. Como a regra é objeto, o sistema mostra qual ramo disparou — informação que um booleano opaco não daria.

Trade-off: Regras temporais ('por 5 minutos') não cabem numa avaliação sem estado; a gramática precisa de operadores com memória, e a simplicidade original começa a se perder.

Filtros salvos em ferramenta interna

Cenário

Um back-office permite montar filtros compostos sobre pedidos e salvá-los como visões compartilhadas entre a equipe.

Como o padrão entra

O filtro é uma árvore serializada. Ele pode ser validado antes de salvar, exibido de volta na interface e traduzido para a consulta do banco.

Trade-off: Serializar a árvore cria um formato persistido: renomear um operador ou mudar sua semântica quebra os filtros que os usuários salvaram meses atrás.

Passo a passoComo montar o Interpreter
  1. 1

    Delimitar a gramática

    Poucas regras estáveis (E, OU, NÃO, comparação). Se a linguagem tende a crescer sem freio, prefira uma engine pronta.

  2. 2

    Uma classe por regra

    Terminais leem o contexto; não-terminais combinam filhos. Cada operador novo é uma classe — não um ramo num switch.

  3. 3

    Montar a árvore

    A frase vira objetos aninhados (parser ou UI). Avaliar é chamar `interpretar` na raiz e deixar a recursão descer.

  4. 4

    Manter terminais puros

    Folhas só leem o contexto já carregado. I/O, banco ou API no terminal destroem previsibilidade e curto-circuito.

O terminal que consulta o banco

Compila, roda, passa no code review. E quebra depois.

A gramática existe (Campo, E, OU) — mas o terminal faz await db.ler a cada folha. A árvore deixou de ser avaliação previsível e virou cascata de I/O.

class Campo implements Expressao {
  constructor(private nome: string, private valor: string) {}

  async interpretar(ctx: Contexto): Promise<boolean> {
    // "Terminal" que consulta o banco a cada folha
    const atual = await db.ler(this.nome);
    return atual === this.valor;
  }
}

// Avaliar a arvore vira cascata de I/O; curto-circuito de E/OU
// muda quantas queries rodam. Terminal deve so ler o contexto.

O que quebra

  • No curto-circuitoE/OU muda quantas queries rodam conforme a ordem dos ramos — custo e side effects imprevisíveis.
  • No testeAvaliar uma expressão exige banco (ou mock) em cada folha; a regra deixa de ser dado puro.
  • Na auditoriaExplicar 'por que entrou' fica opaco: o resultado depende do estado do banco no instante da folha.

A saída: Carregue o contexto antes; terminais só comparam o que já está em memória. Se precisar de dados externos, resolva na borda — não dentro da árvore.

08Cuidado

Armadilhas

  1. A gramática cresce sem parar

    Começa com E, OU e NÃO; em seis meses tem aritmética, funções de data, variáveis e precedência. Cada adição é uma classe e novas interações a testar. Se a gramática está crescendo todo mês, o padrão é a ferramenta errada — um parser gerado ou uma linguagem de expressão pronta escala muito melhor.

  2. Achar que o padrão inclui o parser

    O Interpreter avalia a árvore; ele não transforma texto em árvore. Times descobrem isso depois de adotar o padrão e acabam escrevendo um parser à mão, com todos os problemas clássicos de precedência e mensagens de erro. Se a entrada for texto livre, planeje o parser desde o início.

  3. Terminal com efeito colateral

    Um terminal que consulta banco parece prático e destrói as propriedades do padrão: a avaliação deixa de ser previsível, o custo passa a depender do formato da árvore e curtos-circuitos de E/OU fazem o número de consultas variar de forma misteriosa. Terminais devem ler apenas o contexto já carregado.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Existe uma mini-linguagem estável de regras que muda sem deploy.
  • As regras precisam ser guardadas, versionadas e auditadas como dado.
  • Você precisa explicar por que uma regra deu determinado resultado.

Evite quando

3
  • A gramática é grande ou cresce constantemente.
  • Desempenho é crítico e o volume é alto.
  • As regras são poucas e estáveis — funções bastam.

Testar o que ficou

3 armadilhas de Interpreter viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Engines de regexA expressão é uma linguagem própria, compilada numa árvore que depois é avaliada contra o texto.
  • Template engines{{ nome }} é uma gramática pequena com um avaliador que a resolve contra um contexto.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Árvore da linguagem → avaliação.
const n = parse("a AND b").avaliar(ctx);

O que ele cobra

2 saltos a mais entre a chamada e o efeito.

  • Uma classe por regra gramatical, que cresce rápido conforme a linguagem ganha construções
  • Interpretar a árvore a cada execução costuma ser mais lento que compilar ou usar uma engine pronta

Não vale se: a linguagem é complexa ou muda muito — aí um parser gerado ou uma engine existente supera a gramática feita à mão.

Onde isto aparece

Continue por aqui