Un mixin es un fragmento de código que permite inyectar funcionalidad en una clase sin utilizar herencia tradicional. A diferencia de la herencia, que define una relación de tipo “es un” (un Perro es un Animal), los mixins definen una relación de “tiene un comportamiento” (un Perro tiene la capacidad de Caminar). En Dart, esto es la alternativa técnica a la herencia múltiple, la cual no está permitida en el lenguaje para evitar problemas de ambigüedad estructural.
Para entender cómo funcionan, debemos mirar la linealización de clases. Cuando aplicas un mixin mediante la palabra clave with, el compilador no simplemente añade métodos; crea una nueva clase anónima que envuelve a la clase original, creando una cadena de delegación. Por eso, si tienes dos mixins con el mismo método, el orden importa: el último mixin en la lista de with tiene la prioridad y “gana” la implementación, sobrescribiendo a los anteriores en la cadena.
¿Cuándo deberías usarlos? Utilízalos cuando quieras compartir lógica entre clases que no tienen una relación jerárquica clara. Por ejemplo, si quieres que tanto una Persona como un Robot tengan la capacidad de Caminar, no puedes hacer que ambos hereden de una clase Caminante sin complicar tu árbol de clases; en su lugar, creas un mixin Caminante.
Sin embargo, hay un riesgo: si aplicas múltiples mixins que contienen métodos con la misma firma, el compilador no lanzará un error, pero el comportamiento de tu objeto cambiará según el orden en que los declares, lo que puede derivar en errores de lógica difíciles de rastrear si no dominas la prioridad de resolución.
Para restringir el alcance de un mixin, usamos la cláusula on. Esto es vital cuando el mixin necesita acceder a métodos o propiedades de una clase específica. Si declaras mixin Volar on Animal, garantizas que solo se pueda aplicar a clases que extiendan de Animal, dándole al mixin permiso para llamar a los métodos de Animal de forma segura. Con la llegada de Dart 3 [disponible desde Dart 3.0], también tenemos mixin class, una estructura híbrida que puede funcionar tanto como una clase base (usando extends) como un mixin (usando with), siempre que su constructor sea por defecto o constante.
// Un mixin simple para utilidades de log
mixin Logger {
void log(String mensaje) => print('[LOG]: $mensaje');
}
// Clase base para nuestra jerarquía de máquinas
abstract class Machine {
final String id;
Machine(this.id);
// Método que las subclases deben implementar
void operate();
}
// Mixin restringido: solo puede aplicarse a subclases de Machine
// Esto le permite llamar a 'operate()' de forma segura.
mixin Flyer on Machine {
void takeoff() {
log('Iniciando secuencia de despegue para $id');
operate();
}
}
// mixin class: Puede usarse como clase base o como mixin
mixin class GPS {
double latitud = 0.0;
void updateLocation(double nuevaLat) {
latitud = nuevaLat;
print('Coordenada actualizada: $latitud');
}
}
// Implementación final combinando composición y herencia
class Drone extends Machine with Logger, Flyer, GPS {
Drone(super.id);
@override
void operate() {
print('Motores de $id en marcha.');
}
void executeMission(double destinoLat) {
log('Iniciando misión de reconocimiento.');
updateLocation(destinoLat);
takeoff();
}
}
void main() {
final miDrone = Drone('DRN-99');
miDrone.executeMission(40.4167);
}
Análisis del ejemplo
En el código anterior, observa cómo Drone construye su identidad mediante la combinación de tres fuentes distintas. Al usar with Logger, Flyer, GPS, estamos construyendo una línea de ejecución.
Primero, Drone hereda la propiedad id de Machine. Al aplicar Logger, Drone adquiere el método log(). Cuando Flyer es aplicado, el compilador verifica que Drone sea una Machine (lo cual se cumple). Debido a que Flyer tiene la restricción on Machine, puede invocar operate() dentro de su método takeoff(), y como Drone es la clase que finalmente implementa operate(), se ejecuta la lógica específica del dron.
Un detalle crucial es la resolución de GPS. Al ser un mixin class, Drone podría haberlo usado con extends si fuera el primer componente, pero al usar with, se integra en la cadena de herencia lineal. La llamada a updateLocation es posible porque GPS se inyectó en la jerarquía de Drone.
El error frecuente
Un error común ocurre al intentar usar un mixin con un constructor parametrizado. Los mixins, por definición de la arquitectura de la VM de Dart, no pueden tener constructores con parámetros.
// ESTO DARÁ ERROR DE COMPILACIÓN
mixin ErrorMixin {
String name;
ErrorMixin(this.name); // Error: Mixins can't have constructors.
}
// La solución es usar inicializadores en la clase que lo aplica
// o pasar parámetros a través de un método de inicialización.
Si necesitas que un mixin reciba datos, no intentes definir un constructor; define un método de inicialización o utiliza propiedades que la clase base ya posea.
N° 46