🗣️ El Patrón Interpreter: Hablando tu propio idioma
El Patrón de diseño Interpreter es un patrón de comportamiento. Es uno de esos patrones que no se usan todos los días, pero cuando lo necesitas, te salva la vida.
Su objetivo es que dado un lenguaje, definir una representación de su gramática junto con un intérprete que usa esa representación para interpretar sentencias en el lenguaje.
¿Cómo procesas expresiones que siguen una estructura definida (operaciones matemáticas, reglas de negocio, filtros de búsqueda) sin volverte loco con parsers manuales llenos de if/else?
La respuesta es convertir cada regla gramatical en una clase. Así, construyes un árbol de objetos (Árbol de Sintaxis Abstracta o AST) que representa la expresión y lo recorres para obtener el resultado.

🎼 1. La partitura musical
Imagina a un músico leyendo una partitura:
- La partitura está escrita en un lenguaje musical (notas, silencios, claves).
- El músico (El Intérprete) conoce las reglas: sabe que una redonda dura 4 tiempos y una negra 1 tiempo.
- Al leer (interpretar) la partitura, convierte esos símbolos en sonidos.
Cada símbolo individual (Do, Re, Mi) es una expresión terminal: no se descompone más. Un acorde o un compás es una expresión no terminal: se compone de otras expresiones.
El patrón Interpreter funciona exactamente así: defines las reglas del lenguaje como clases y las combinas en un árbol que se puede recorrer para tocar la canción.
¿Qué tipo de patrón de diseño es Interpreter?
🛠️ 2. Los roles del patrón
La interfaz que declara el método interpretar(). Todas las reglas gramaticales la implementan. Es el contrato común.
Los elementos básicos del lenguaje: números, variables, literales. Son las hojas del árbol. No contienen otras expresiones.
Las reglas compuestas: suma, resta, AND, OR... Contienen referencias a otras expresiones (terminales o no). Son las ramas del árbol.
Información global que las expresiones necesitan para interpretarse: valores de variables, tablas de símbolos, configuración. Se pasa como parámetro.
Cuando llamas a interpretar() en la raíz del árbol, cada nodo delega a sus hijos recursivamente hasta llegar a las hojas, que devuelven sus valores directamente.
¿Qué diferencia hay entre una expresión terminal y una no terminal?
🧑💻 3. Ejemplo práctico en PHP: Calculadora
Vamos a crear un intérprete para sumar, restar y multiplicar números. La expresión "(10 + 5) - 3" se convierte en un árbol de objetos:
Resta
/ \
Suma Numero(3)
/ \
Numero(10) Numero(5)
<?php
// 1. Expresión Abstracta
interface Expresion
{
public function interpretar(): int;
}
// 2. Expresión Terminal (Número)
class Numero implements Expresion
{
private int $valor;
public function __construct(int $valor)
{
$this->valor = $valor;
}
public function interpretar(): int
{
return $this->valor;
}
}
// 3. Expresiones No Terminales (Operaciones)
class Sumar implements Expresion
{
public function __construct(
private Expresion $izquierda,
private Expresion $derecha
) {}
public function interpretar(): int
{
return $this->izquierda->interpretar() + $this->derecha->interpretar();
}
}
class Restar implements Expresion
{
public function __construct(
private Expresion $izquierda,
private Expresion $derecha
) {}
public function interpretar(): int
{
return $this->izquierda->interpretar() - $this->derecha->interpretar();
}
}
class Multiplicar implements Expresion
{
public function __construct(
private Expresion $izquierda,
private Expresion $derecha
) {}
public function interpretar(): int
{
return $this->izquierda->interpretar() * $this->derecha->interpretar();
}
}
// 🔹 4. Cliente: construimos el árbol
// Expresión: (10 + 5) - 3
$suma = new Sumar(new Numero(10), new Numero(5));
$resta = new Restar($suma, new Numero(3));
echo "(10 + 5) - 3 = " . $resta->interpretar() . "\n"; // 12
// Expresión más compleja: (4 * 3) + (10 - 2)
$multi = new Multiplicar(new Numero(4), new Numero(3));
$resta2 = new Restar(new Numero(10), new Numero(2));
$total = new Sumar($multi, $resta2);
echo "(4 * 3) + (10 - 2) = " . $total->interpretar() . "\n"; // 20
Cada operación es una clase. Añadir una nueva operación (dividir, módulo, potencia) es crear una clase nueva que implemente Expresion. No tocas nada existente, principio OCP.
¿Qué estructura de datos se forma al usar el patrón Interpreter?
🔧 4. Ejemplo con Contexto: Variables
La calculadora está bien, pero el Interpreter brilla de verdad cuando introduces variables. El Contexto es un mapa donde guardas los valores de las variables:
// Contexto: almacena valores de variables
class Contexto
{
private array $variables = [];
public function set(string $nombre, int $valor): void
{
$this->variables[$nombre] = $valor;
}
public function get(string $nombre): int
{
if (!isset($this->variables[$nombre])) {
throw new \RuntimeException("Variable '$nombre' no definida");
}
return $this->variables[$nombre];
}
}
// Expresión Abstracta con contexto
interface Expresion
{
public function interpretar(Contexto $ctx): int;
}
// Terminal: número literal
class Numero implements Expresion
{
public function __construct(private int $valor) {}
public function interpretar(Contexto $ctx): int
{
return $this->valor;
}
}
// Terminal: variable (busca su valor en el contexto)
class Variable implements Expresion
{
public function __construct(private string $nombre) {}
public function interpretar(Contexto $ctx): int
{
return $ctx->get($this->nombre);
}
}
// No terminal: suma
class Sumar implements Expresion
{
public function __construct(
private Expresion $izquierda,
private Expresion $derecha
) {}
public function interpretar(Contexto $ctx): int
{
return $this->izquierda->interpretar($ctx) + $this->derecha->interpretar($ctx);
}
}
// Uso: precio + iva
$ctx = new Contexto();
$ctx->set('precio', 100);
$ctx->set('iva', 21);
$expresion = new Sumar(new Variable('precio'), new Variable('iva'));
echo "precio + iva = " . $expresion->interpretar($ctx) . "\n"; // 121
// Cambias el contexto y la misma expresión da otro resultado
$ctx->set('precio', 200);
echo "precio + iva = " . $expresion->interpretar($ctx) . "\n"; // 221
La misma expresión, distintos datos. Esto es exactamente lo que hacen los motores de plantillas, las hojas de cálculo y los motores de reglas de negocio.
¿Para qué sirve el Contexto en el patrón Interpreter?
🌍 5. Casos de uso reales
El Interpreter no es solo para calculadoras. Aparece en sitios que no esperarías:
"Si el usuario es premium Y lleva más de 1 año, aplica 20% de descuento". Las reglas se modelan como expresiones: And(EsPremium(), AntiguedadMayor(1)).
"Precio > 50 AND categoría = 'ropa'". Cada condición es una expresión terminal, los AND/OR son no terminales. Construyes el filtro dinámicamente.
Un motor de regex internamente funciona como un Interpreter. Cada parte del patrón (literal, cuantificador, grupo) es una expresión que se compone recursivamente.
Query builders como Doctrine o Eloquent construyen un AST internamente. Cada where(), orderBy() y join() añade nodos al árbol.
Ejemplo: Motor de reglas de descuento
// Expresiones booleanas para reglas de negocio
interface Regla
{
public function evaluar(array $usuario): bool;
}
class EsPremium implements Regla
{
public function evaluar(array $usuario): bool
{
return $usuario['tipo'] === 'premium';
}
}
class AntiguedadMayor implements Regla
{
public function __construct(private int $años) {}
public function evaluar(array $usuario): bool
{
return $usuario['antiguedad'] > $this->años;
}
}
class And_ implements Regla
{
public function __construct(
private Regla $izquierda,
private Regla $derecha
) {}
public function evaluar(array $usuario): bool
{
return $this->izquierda->evaluar($usuario) && $this->derecha->evaluar($usuario);
}
}
class Or_ implements Regla
{
public function __construct(
private Regla $izquierda,
private Regla $derecha
) {}
public function evaluar(array $usuario): bool
{
return $this->izquierda->evaluar($usuario) || $this->derecha->evaluar($usuario);
}
}
// Regla: Es premium AND lleva más de 2 años
$regla = new And_(new EsPremium(), new AntiguedadMayor(2));
$usuario = ['tipo' => 'premium', 'antiguedad' => 3];
echo $regla->evaluar($usuario) ? "Descuento aplicado\n" : "Sin descuento\n";
// Descuento aplicado
$usuario2 = ['tipo' => 'basico', 'antiguedad' => 5];
echo $regla->evaluar($usuario2) ? "Descuento aplicado\n" : "Sin descuento\n";
// Sin descuento (no es premium)
Las reglas se pueden construir dinámicamente (desde una BD, un JSON, un panel de admin), combinarse sin límite y evaluarse sin tocar el código. Eso es muy potente.
📝 6. El Parser: Convertir texto en árbol
En los ejemplos anteriores, construimos el árbol a mano. En la práctica, necesitas un parser que convierta un string como "10 + 5 - 3" en el árbol de objetos:
class Parser
{
public function parsear(string $expresion): Expresion
{
$tokens = explode(' ', $expresion);
$resultado = new Numero((int)$tokens[0]);
for ($i = 1; $i < count($tokens); $i += 2) {
$operador = $tokens[$i];
$numero = new Numero((int)$tokens[$i + 1]);
$resultado = match ($operador) {
'+' => new Sumar($resultado, $numero),
'-' => new Restar($resultado, $numero),
'*' => new Multiplicar($resultado, $numero),
default => throw new \RuntimeException("Operador desconocido: $operador"),
};
}
return $resultado;
}
}
$parser = new Parser();
$arbol = $parser->parsear("10 + 5 - 3");
echo $arbol->interpretar(); // 12
Este parser es muy básico (no maneja paréntesis ni precedencia de operadores), pero ilustra la idea: el parser convierte texto en un AST, y el Interpreter lo evalúa. Son dos fases separadas.
🔄 7. Comparativa con otros patrones
| Patrón | Propósito | Diferencia clave |
|---|---|---|
| Interpreter | Definir gramática y evaluar expresiones | Cada regla gramatical es una clase que se combina en un árbol |
| Visitor | Añadir operaciones a un árbol sin modificar sus clases | Se usa junto con Interpreter para separar la evaluación de la estructura |
| Composite | Tratar objetos individuales y grupos uniformemente | Interpreter es un Composite especializado: el AST es un árbol Composite |
| Strategy | Intercambiar algoritmos en runtime | Strategy cambia un algoritmo; Interpreter combina múltiples reglas en un árbol |
| Chain of Responsibility | Pasar petición por una cadena | CoR es lineal; Interpreter es un árbol con evaluación recursiva |
Un dato importante: el AST del Interpreter es básicamente un Composite. Las expresiones terminales son las hojas y las no terminales son los compuestos. Si ya conoces Composite, el Interpreter te resultará familiar.
¿Qué patrón se suele combinar con Interpreter para recorrer el árbol?
⚖️ 8. Relación con SOLID
Cada expresión (Sumar, Restar, Numero) tiene una sola responsabilidad: saber interpretarse a sí misma.
Añadir Multiplicar o Dividir = crear una clase nueva. Sin tocar Sumar ni Restar ni ninguna otra.
Las expresiones no terminales dependen de la abstracción Expresion, no de clases concretas. Sumar no sabe si sus hijos son números, variables u otras operaciones.
Cualquier Expresion puede usarse donde se espera la interfaz: un Numero, una Sumar o una Variable. El árbol funciona igual.
¿Qué relación tiene Interpreter con Composite?
✅ 9. Ventajas y desventajas
- Extensible: Añadir nuevas reglas gramaticales = crear una clase nueva.
- Gramática explícita: Las reglas están en el código como clases, no escondidas en ifs.
- Composable: Puedes combinar expresiones en árboles de cualquier profundidad.
- Reutilizable: La misma expresión se evalúa con distintos contextos.
- Testeable: Cada expresión se puede testear de forma aislada.
- Muchas clases: Cada regla gramatical = una clase. Gramáticas complejas = cientos de clases.
- Rendimiento: Recorrer un árbol recursivamente es más lento que un parser optimizado.
- Gramáticas complejas: Para lenguajes completos (SQL, JavaScript), el Interpreter se queda corto.
- Parser necesario: Convertir texto en AST requiere un parser aparte, que puede ser complejo.
⚠️ 10. Errores comunes
1. Usarlo para gramáticas demasiado complejas
Si tu gramática tiene 50+ reglas, vas a acabar con cientos de clases. En ese caso, usa una herramienta de parsing como ANTLR, PEG parsers o un parser generator:
// ❌ MAL: interpretar SQL completo con Interpreter
class Select implements Expresion { /* ... */ }
class From implements Expresion { /* ... */ }
class Where implements Expresion { /* ... */ }
class Join implements Expresion { /* ... */ }
class GroupBy implements Expresion { /* ... */ }
class Having implements Expresion { /* ... */ }
class OrderBy implements Expresion { /* ... */ }
// ... 30 clases más
// ✅ BIEN: usa Interpreter para gramáticas simples (5-15 reglas)
// Para gramáticas complejas, usa un parser generator
¿Por qué es un error usar Interpreter para gramáticas muy complejas (50+ reglas)?
2. No separar el Parser del Interpreter
El parser (convertir texto en árbol) y el interpreter (evaluar el árbol) son dos responsabilidades distintas. Si mezclas ambas, el código se vuelve inmantenible:
// ❌ MAL: la expresión se parsea y se evalúa en el mismo sitio
class Sumar {
public function interpretar(string $expresion): int {
$partes = explode('+', $expresion); // Parsing dentro de interpretar
return (int)$partes[0] + (int)$partes[1];
}
}
// ✅ BIEN: el parser crea el árbol, el interpreter lo evalúa
$arbol = $parser->parsear("10 + 5"); // Parser
echo $arbol->interpretar(); // Interpreter
3. Olvidar el Contexto
Si tus expresiones necesitan variables o configuración, no las hardcodees en las clases. Usa un objeto Contexto:
// ❌ MAL: la variable busca su valor en una global
class Variable implements Expresion {
public function interpretar(): int {
return $GLOBALS[$this->nombre]; // 💀
}
}
// ✅ BIEN: el contexto se pasa como parámetro
class Variable implements Expresion {
public function interpretar(Contexto $ctx): int {
return $ctx->get($this->nombre);
}
}
4. Ignorar la precedencia de operadores
Si tu parser no maneja precedencia, 2 + 3 * 4 se evaluará como (2 + 3) * 4 = 20 en vez de 2 + (3 * 4) = 14. Para gramáticas simples puedes manejarlo con paréntesis obligatorios. Para gramáticas reales, necesitas un parser con soporte de precedencia.
🤔 11. ¿Cuándo usarlo y cuándo no?
- La gramática es simple (5-15 reglas)
- Las reglas cambian frecuentemente o se configuran dinámicamente
- Necesitas un DSL (Domain-Specific Language) pequeño
- Quieres reglas de negocio configurables sin tocar código
- Necesitas evaluar expresiones que los usuarios definen (fórmulas, filtros)
- La gramática es compleja (50+ reglas, usa un parser generator)
- El rendimiento es crítico (la recursividad es más lenta que un parser optimizado)
- La gramática nunca cambia (un método hardcodeado es más simple)
- Ya existe una herramienta establecida para tu caso (regex, SQL, JSON Schema...)
Si tu “lenguaje” cabe en una servilleta (pocas reglas, combinables), usa Interpreter. Si necesitas un libro para explicar la gramática, usa un parser generator.
¿Cuándo es ideal usar el patrón Interpreter?
💡 12. Conclusión
El Patrón Interpreter es el más académico de los patrones de diseño, pero tiene aplicaciones prácticas muy reales: motores de reglas de negocio, filtros de búsqueda dinámicos, calculadoras de fórmulas, validaciones configurables…
La idea es convertir cada regla gramatical en una clase, combinarlas en un árbol, y recorrer ese árbol para evaluarlo. Es un Composite especializado. Y como cada regla es una clase, añadir nuevas reglas es crear una clase nueva y ya.
No lo uses para parsear SQL ni para crear un compilador. Pero para un DSL pequeño con 10 reglas que se combinan, es perfecto.
EA, ¡saluditos y nos vemos en los bares! 🍻