Patrón Interpreter
Define una representación para la gramática de un lenguaje junto con un intérprete que usa la representación para interpretar oraciones. Un patrón de diseño de comportamiento para mini-lenguajes.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Patrón Interpreter
Resumen
El Patrón Interpreter es un patrón de diseño de comportamiento que define una representación para la gramática de un lenguaje junto con un intérprete que usa la representación para interpretar oraciones en el lenguaje. Es ideal para construir lenguajes específicos de dominio (DSL), evaluadores de expresiones, analizadores de consultas y motores de reglas.
Cuándo usarlo
Usa el Patrón Interpreter cuando:
- Tengas una gramática simple que necesita ser analizada y evaluada frecuentemente
- Quieras construir un lenguaje específico de dominio (DSL) para configuración o reglas
- Las oraciones del lenguaje puedan representarse como árboles de sintaxis abstracta (AST)
- La simplicidad de la gramática hace que un generador de parsers (ANTLR, yacc) sea excesivo
- Ejemplos: expresiones aritméticas, consultas tipo SQL, motores de reglas booleanas, motores regex
Solución
Python
from abc import ABC, abstractmethod
from typing import Any
class Expression(ABC):
@abstractmethod
def interpret(self, context: dict) -> Any:
pass
class Number(Expression):
def __init__(self, value: float):
self.value = value
def interpret(self, context: dict) -> float:
return self.value
class Variable(Expression):
def __init__(self, name: str):
self.name = name
def interpret(self, context: dict) -> Any:
return context.get(self.name, 0)
class Add(Expression):
def __init__(self, left: Expression, right: Expression):
self.left = left
self.right = right
def interpret(self, context: dict) -> float:
return self.left.interpret(context) + self.right.interpret(context)
class Subtract(Expression):
def __init__(self, left: Expression, right: Expression):
self.left = left
self.right = right
def interpret(self, context: dict) -> float:
return self.left.interpret(context) - self.right.interpret(context)
# Uso: evaluar "(10 + x) - 5" donde x = 3
expression = Subtract(
Add(Number(10), Variable("x")),
Number(5)
)
context = {"x": 3}
print(expression.interpret(context)) # 8
JavaScript
class Expression {
interpret(context) {
throw new Error("Las subclases deben implementar interpret()");
}
}
class NumberLiteral extends Expression {
constructor(value) {
super();
this.value = value;
}
interpret(context) {
return this.value;
}
}
class Variable extends Expression {
constructor(name) {
super();
this.name = name;
}
interpret(context) {
return context[this.name] ?? 0;
}
}
class Add extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret(context) {
return this.left.interpret(context) + this.right.interpret(context);
}
}
class Subtract extends Expression {
constructor(left, right) {
super();
this.left = left;
this.right = right;
}
interpret(context) {
return this.left.interpret(context) - this.right.interpret(context);
}
}
// Uso: evaluar "(10 + x) - 5" donde x = 3
const expression = new Subtract(
new Add(new NumberLiteral(10), new Variable("x")),
new NumberLiteral(5)
);
console.log(expression.interpret({ x: 3 })); // 8
Java
import java.util.Map;
public interface Expression {
double interpret(Map<String, Double> context);
}
public class NumberLiteral implements Expression {
private final double value;
public NumberLiteral(double value) {
this.value = value;
}
public double interpret(Map<String, Double> context) {
return value;
}
}
public class Variable implements Expression {
private final String name;
public Variable(String name) {
this.name = name;
}
public double interpret(Map<String, Double> context) {
return context.getOrDefault(name, 0.0);
}
}
public class Add implements Expression {
private final Expression left, right;
public Add(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public double interpret(Map<String, Double> context) {
return left.interpret(context) + right.interpret(context);
}
}
public class Subtract implements Expression {
private final Expression left, right;
public Subtract(Expression left, Expression right) {
this.left = left;
this.right = right;
}
public double interpret(Map<String, Double> context) {
return left.interpret(context) - right.interpret(context);
}
}
// Uso
Expression expr = new Subtract(
new Add(new NumberLiteral(10), new Variable("x")),
new NumberLiteral(5)
);
System.out.println(expr.interpret(Map.of("x", 3.0))); // 8.0
Explicación
El Patrón Interpreter se construye alrededor de un árbol de sintaxis abstracta (AST):
- Expresión Abstracta (
Expression): Declara la interfazinterpret() - Expresión Terminal (
NumberLiteral,Variable): Representa nodos hoja en el AST - Expresión No Terminal (
Add,Subtract): Representa nodos compuestos que combinan otras expresiones - Contexto (
dict/Map): Mantiene estado global (variables) disponible durante la interpretación
El cliente construye un AST y luego llama interpret() en el nodo raíz, que evalúa recursivamente el árbol.
Variantes
| Variante | Descripción | Caso de uso |
|---|---|---|
| Tree Walker | Recorre un AST pre-construido | Motores de reglas, evaluadores de consultas |
| Visitor + Interpreter | Separa la lógica de evaluación de los nodos del árbol | Cuando las estrategias de evaluación varían |
| Interpreter Memoizado | Cachea resultados de sub-expresiones | Evaluaciones repetidas con el mismo contexto |
| Compiler + VM | Compila a bytecode, luego ejecuta | Ejecución repetida de alto rendimiento |
Lo que funciona
- Mantén la gramática simple — las gramáticas complejas se manejan mejor con generadores de parsers
- Construye el AST primero, luego interpreta — no interpretes durante el parsing
- Usa nodos de expresión inmutables para seguridad de hilos y reproducibilidad
- Documenta la precedencia de operadores claramente si soportas múltiples operadores
- Considera agregar
toString()para depuración y serialización de expresiones
Errores comunes
- Usar Interpreter para lenguajes complejos donde un generador de parsers (ANTLR, PEG.js) es más apropiado
- Mezclar la lógica de parsing con la lógica de interpretación, creando código spaghetti
- No manejar errores de tipo o variables faltantes gracefulmente
- Construir el AST manualmente en lugar de usar un parser para gramáticas más grandes
- Ignorar la precedencia de operadores, llevando a orden de evaluación incorrecto
Preguntas frecuentes
P: ¿Cuándo debería usar Interpreter en lugar de un generador de parsers? R: Usa Interpreter para gramáticas muy simples con pocas reglas que cambian infrecuentemente. Para cualquier cosa más allá de aritmética básica o expresiones booleanas, usa ANTLR, PEG.js o una herramienta similar.
P: ¿Puedo combinar Interpreter con Visitor? R: Sí, y a menudo deberías. El Patrón Interpreter define la estructura del AST; el Patrón Visitor agrega operaciones (interpretar, serializar, optimizar) sin modificar los nodos del AST.
¿Es este patrón adecuado para proyectos pequeños?
Para proyectos pequeños con pocos componentes, este patrón puede añadir complejidad innecesaria. Empieza simple e introduce el patrón cuando sientas el problema que resuelve.
¿Cómo se compara este patrón con alternativas?
Cada patrón hace diferentes trade-offs. Revisa la tabla de variantes arriba y considera tus restricciones específicas: tamaño del equipo, requisitos de rendimiento y planes de escalado.
¿Puedo aplicar este patrón parcialmente?
Sí. Muchos equipos adoptan patrones incrementalmente. Empieza con la idea central y añade sofisticación según sea necesario. El patrón es una guía, no un blueprint estricto.
Temas Avanzados
Escenario: Interpreter para DSL de Configuracion
// Interpreter: evaluar expresiones de un DSL
// Gramatica: SET key value | GET key | DEL key | EXISTS key
interface Expression {
interpret(context: Map<string, string>): string;
}
// Terminal: SET
class SetExpression implements Expression {
constructor(private key: string, private value: string) {}
interpret(context: Map<string, string>): string {
context.set(this.key, this.value);
return `OK: ${this.key} = ${this.value}`;
}
}
// Terminal: GET
class GetExpression implements Expression {
constructor(private key: string) {}
interpret(context: Map<string, string>): string {
const value = context.get(this.key);
return value !== undefined ? value : `NIL: ${this.key}`;
}
}
// Terminal: DEL
class DelExpression implements Expression {
constructor(private key: string) {}
interpret(context: Map<string, string>): string {
const existed = context.delete(this.key);
return existed ? `OK: ${this.key} deleted` : `NIL: ${this.key}`;
}
}
// Non-terminal: secuencia de expresiones
class SequenceExpression implements Expression {
constructor(private expressions: Expression[]) {}
interpret(context: Map<string, string>): string {
return this.expressions.map(e => e.interpret(context)).join("\n");
}
}
// Parser: convertir texto a arbol de expresiones
class ConfigParser {
parse(input: string): Expression {
const lines = input.trim().split("\n");
const expressions: Expression[] = [];
for (const line of lines) {
const parts = line.split(" ");
const cmd = parts[0].toUpperCase();
if (cmd === "SET") expressions.push(new SetExpression(parts[1], parts.slice(2).join(" ")));
else if (cmd === "GET") expressions.push(new GetExpression(parts[1]));
else if (cmd === "DEL") expressions.push(new DelExpression(parts[1]));
else throw new Error(`Unknown command: ${cmd}`);
}
return new SequenceExpression(expressions);
}
}
// Uso
const parser = new ConfigParser();
const context = new Map<string, string>();
const program = parser.parse("SET name Alice\nSET role admin\nGET name\nDEL role");
console.log(program.interpret(context));
// OK: name = Alice
// OK: role = admin
// Alice
// OK: role deleted
Lecciones:
- Interpreter evalua expresiones de un DSL (Domain Specific Language)
- Cada expresion implementa interpret(): patron recursivo
- Terminal: SET, GET, DEL. Non-terminal: Sequence (compone)
- El parser convierte texto en arbol de expresiones
- Ideal para configs, queries, reglas de negocio simples
- Para DSLs complejos, usar parser generators (ANTLR, nearley)
### Interpreter vs Visitor: cual uso?
Interpreter evalua un arbol de expresiones: cada nodo sabe como interpretarse. Visitor recorre un arbol de objetos: el visitor sabe que hacer con cada nodo. Interpreter es para ejecutar un DSL. Visitor es para operaciones sobre una estructura. Usa Interpreter cuando necesitas un lenguaje custom (config, query, rules). Usa Visitor cuando necesitas anadir operaciones a una estructura sin modificar las clases. Recursos Relacionados
Chain of Responsibility Pattern
Pass requests along a chain of handlers until one handles it. A behavioral design pattern for decoupling senders and receivers.
PatternStrategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
PatternCommand Pattern
Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.
PatternMemento Pattern
Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.
PatternTemplate Method Pattern
Define the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm's structure. A behavioral design pattern.
PatternIterator Pattern
Provide a way to access elements of a collection sequentially without exposing its underlying representation. A behavioral design pattern for traversal.