Los patterns [disponible desde Dart 3.0] son una forma avanzada de inspeccionar la estructura de un valor para decidir qué hacer con él. En lugar de usar una serie de comprobaciones manuales con is y luego hacer un cast (lo que llamamos type casting), los patrones permiten hacer dos cosas simultáneamente: verificar si un valor tiene la forma esperada (matching) y extraer sus componentes internos en variables locales (destructuring).
Funciona de esta manera porque el motor de Dart ha unificado el concepto de “forma” para diferentes tipos de datos. Antes, para extraer datos de un objeto o una lista, tenías que acceder a sus propiedades o índices uno por uno. Ahora, un patrón actúa como un molde: si el valor “encaja” en el molde, el compilador automáticamente “descompone” el objeto y te entrega sus piezas.
Deberías usar patrones siempre que trabajes con estructuras compuestas como Records, Lists, Maps o instancias de clases propias, y cuando necesites lógica condicional basada en la forma de los datos (por ejemplo, “si es una lista de dos enteros, haz X”). También son ideales para limpiar el ruido visual de los if (x is Type) seguidos de var y = x as Type.
Si los usas de forma incorrecta, especialmente en un switch expression, lo más probable es que el compilador te detenga con un error de exhaustividad (exhaustiveness error). Un switch expression debe cubrir absolutamente todos los casos posibles del tipo de dato que estás evaluando. Si dejas un hueco, el código no compilará.
class Alarma {
final String mensaje;
final int nivel;
Alarma(this.mensaje, this.nivel);
}
void main() {
// Una colección heterogénea para demostrar la versatilidad
final eventos = <Object?>[
(1, 'Sonda_A', 35.5), // Un Record
[10, 20], // Una List
Alarma('Incendio', 5), // Un objeto de clase personalizada
"Error de sistema", // Un String
150, // Un Int (para probar patrones relacionales)
42, // Un Int
null, // Un valor nulo
];
for (final e in eventos) {
print(procesarEvento(e));
}
}
String procesarEvento(Object? evento) {
// El switch expression requiere exhaustividad total
return switch (evento) {
// 1. Record pattern + Guard clause (when)
(int id, String nombre, double temp) when temp > 30.0 =>
'¡Alerta en $nombre (ID: $id)! Calor extremo: $temp°C',
// 2. Record pattern simple (matching de la forma)
(int id, String nombre, double temp) =>
'Sensor $nombre ($id) reporta $temp°C',
// 3. List pattern (desestructuración de posición)
[int primero, int segundo] => 'Coordenadas: $primero y $segundo',
// 4. Object pattern (desestructurando propiedades de una clase)
Alarma(mensaje: var msg, nivel: var n) when n > 3 => '¡CRÍTICO! $msg (Nivel $n)',
Alarma(mensaje: var msg) => 'Aviso: $msg',
// 5. Type pattern + Relational pattern (dentro de una guarda)
int i when i > 100 => 'Valor numérico muy alto: $i',
int i => 'Número entero: $i',
// 6. String pattern (Type pattern)
String s => 'Mensaje de texto: $s',
// 7. Null-check pattern (disponible desde Dart 3.0)
null => 'No se recibió ningún dato (nulo)',
// 8. Wildcard pattern (el comodín que garantiza la exhaustividad)
_ => 'Evento de tipo desconocido',
};
}
Desglose del ejemplo
En el switch expression de procesarEvento, hemos aplicado varios conceptos clave:
- Record Pattern: En la primera línea,
(int id, String nombre, double temp)no solo verifica queeventosea unRecordcon tres elementos, sino que también extrae sus valores y los vincula a las variablesid,nombreytemp. Si el primer elemento no fuera unint, el patrón fallaría y pasaría al siguiente caso. - Object Pattern: Cuando tratamos con la clase
Alarma, usamosAlarma(mensaje: var msg, nivel: var n). Aquí, el patrón busca propiedades con esos nombres específicos y usa Variable Patterns (var msg) para asignar el contenido de la propiedad a una nueva variable local disponible en esa rama delswitch. - List Pattern: La sintaxis
[int primero, int segundo]es extremadamente potente. Solo coincidirá si la lista tiene exactamente dos elementos y ambos son enteros. Si la lista tuviera tres elementos, este patrón no coincidiría. - Relational & Guard Clauses: Aunque los patrones pueden ser relacionales (como
> 100), en unswitchsobreObject, primero debemos asegurar el tipo con unint i. La cláusulawhen(guard clause) permite añadir condiciones lógicas adicionales que el matching de forma por sí solo no puede expresar. - Wildcard (
_): Este es el “comodín”. Comoeventoes de tipoObject?, el compilador nos obliga a considerar qué pasaría si llega algo que no cumple ninguna de las reglas anteriores. Sin el_, el código no compilaría.
El error frecuente
Un error clásico al empezar con patrones es confundir un Variable Pattern con un Constant Pattern dentro de un switch statement (no expression).
// ERROR SUBTIL
var x = 10;
switch (x) {
case var y: // Esto es un Variable Pattern. Siempre coincide y vincula x a y.
print('Esto siempre se ejecutará');
case 10: // Este caso es inalcanzable (unreachable code)
print('Esto nunca se ejecutará');
}
En el código de arriba, case var y: no está preguntando “¿Es x igual a una variable llamada y?”. Está diciendo: “Cualquier valor que sea, asígnale ese valor a una nueva variable y“. Como cualquier valor siempre puede ser asignado a una variable, el primer caso actúa como un “atrapa-todo”, haciendo que los casos siguientes sean código muerto. Si tu intención es comparar contra un valor constante, usa el valor directamente o una constante definida.
N° 85