Dart: ¿Qué son los mixins?

Es una especie de magia✨

Eduardo CQ
Comunidad Flutter
9 min readSep 29, 2019

--

Hola a todos, les traigo otra traducción mas para la comunidad Flutter en español. Esta vez escrita por el autor Romain Rastel y lo puedes encontrar en el siguiente enlace Dart: What are mixins?.

Cuando yo empecé a aprender Dart, el concepto de mixins era nuevo para mi. Yo vengo del mundo de C#, donde esto no existe (al menos antes de C# 8.0, que yo sepa).
Al principio encontré este concepto algo difícil de entender, hasta que me di cuenta de lo poderoso que era.

Aviso: las especificaciones de Mixins están evolucionando en Dart 2. Algunos de los siguientes contenidos podrían no ser aplicables al momento de leer esto.

🤔¿Por qué necesitas mixins?

Si lenguajes tales como C# no tienen mixins, probablemente no es tan útil, ¿verdad?

Echa un vistazo al siguiente diagrama de herencia de clase:

Tienes una superclase llamada Animal, la cual tiene tres subclases (Mammal, Bird y Fish). En la parte inferior, tienes clases concretas. Los cuadros pequeños representan el comportamiento. Por ejemplo, el cuadro azul indica que una instancia de una clase con este comportamiento puede nadar.

Algunos animales comparten un comportamiento en común: Un gato y una paloma ambos pueden caminar; pero el gato no puede volar (a excepción del gato Nyan😀).
Este tipo de comportamiento es ortogonal a la clasificación, entonces no puedes implementar este comportamiento en las superclases.

Si una clase pudiera tener más de una superclase, podría ser fácil, crearías tres clases: Walker, Swimmer, Flyer. Luego de esto, solo tendrías que heredar Dove y Cat desde la clase Walker. Pero en Dart, cada clase (excepto por Object) tiene exactamente una superclase.

En lugar de heredar desde la clase Walker, podrías implementarla como si fuera una interfaz, pero deberías implementar el comportamiento en múltiples clases, entonces no es una buena solución.

Necesitas una forma de reutilizar código de una clase en múltiples jerarquías de clases. ¿Lo sabes? Los Mixins son exactamente eso:

Los Mixins son una forma de reutilizar código de una clase en múltiples jerarquías de clase.

dartlang.org

Descrito así, suena fácil 😁.

🔒 Restricciones

La función mixin viene con unas pocas restricciones (de dartlang.org):

  • Dart 1.12 o inferior soporta mixins, que debe extender de Object y no debe llamar a super().
  • Dart 1.13 o superior soporta mixins que pueden extenderse desde clases distintas a Object, y pueden llamar a super.method(). Este soporte solo esta disponible por defecto en el Dart VM y en Analyzer detrás de una bandera. Más específicamente, está detrás de la bandera --supermixin en la analyzer línea de comandos. También está disponible en el servidor de análisis, detrás de una opción configurable del cliente. Dart2js y dartdevc no soportan super mixins.
  • En Dart 2.1, mixins se espera que tengan menos restricciones. Por ejemplo, Flutter soporta llamadas mixins super() y extendiendo desde una clase distinta a Object, pero se espera que la sintaxis cambie antes de aparecer en todos los SDKs de Dart. Para más detalles, ver las especificaciones mixin.

📝 Sintaxis

Viste como los mixins pueden ser útiles, vas a ver cómo crearlos y usarlos.

Los mixins son definidos implícitamente a travez de declaraciones de clase ordinarias:

class Walker {
void walk() {
print("I'm walking");
}
}

Si quieres evitar que el mixin sea instanciado o extendido, puedes definirlo así:

abstract class Walker {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory Walker._() => null;

void walk() {
print("I'm walking");
}
}

Para usar un mixin, use la palabra clave with seguido de uno o más nombres de mixin:

class Cat extends Mammal with Walker {}

class Dove extends Bird with Walker, Flyer {}

Definiendo el mixin Walker en la clase Cat, te permite llamar el método walk pero no el método fly (definido en Flyer).

main(List<String> arguments) {
Cat cat = Cat();
Dove dove = Dove();

// A cat can walk.
cat.walk();

// A dove can walk and fly.
dove.walk();
dove.fly();

// A normal cat cannot fly.
// cat.fly(); // Uncommenting this does not compile.
}

🔎 Detalles

Te dije que encontré este concepto algo difícil de entender, pero hasta ahora no es tan difícil, ¿verdad?
Bien, ¿puedes decir cuál es la salida del siguiente programa😵?

class A {
String getMessage() => 'A';
}

class B {
String getMessage() => 'B';
}

class P {
String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
String result = '';

AB ab = AB();
result += ab.getMessage();

BA ba = BA();
result += ba.getMessage();

print(result);
}

Puedes ejecutar este programa en DartPad

Ambas clases AB y BA extienden de la clase P con mixins A y B pero en diferente orden. Todas las tres clases A, B y P tienen un método llamado getMessage.

Primero, llamas al método getMessage de la clase AB, entonces el método getMessage de la clase BA.

¿Entonces, cuál crees que será la salida resultante?
Te estoy dando cinco proposiciones:

A. No compila
B. BA
C. AB
D. BAAB
E. ABBA

🥁🥁🥁 La respuesta es la proposición B! El programa imprime BA.

Creo que has adivinado que el orden en que se declaran los mixins es muy importante.

¿Por qué? ¿Cómo esta funcionando?

✨ Linealización

Cuándo aplicas un mixin a una clase, tenga en cuenta esto:

Los mixins en Dart funcionan creando una nueva clase que superpone la implementación de mixin sobre una superclase para crear una nueva clase — no está “en el costado” sino “en la parte superior” de la superclase, por lo que no hay ninguna ambigüedad en como resolver las búsquedas.

Lasse R. H. Nielsen on StackOverFlow

De hecho, el código

class AB extends P with A, B {}

class BA extends P with B, A {}

es semánticamente equivalente a

class PA = P with A;
class PAB = PA with B;

class AB extends PAB {}

class PB = P with B;
class PBA = PB with A;

class BA extends PBA {}

El diagrama de herencia final puede ser representado así:

Nuevas clases son creadas entre AB y P. Estas clases nuevas son un mix-in entre la superclase P y las clases A y B.

Como puedes ver, no hay herencia múltiple ahí!

Los minxins no es una forma de obtener herencia múltiple en el sentido clásico. Mixins es una forma de abstraer y reutilizar una familia de operaciones y estados. Es similar a la reutilización que obtienes al extender una clase, pero es compatible con herencia-única porque es lineal.

Lasse R. H. Nielsen on StackOverflow.

Una cosa importante a recordar es que el orden en el que se declaran los mixins representan la cadena de herencia, desde la superclase superior a la inferior.

📄 Tipos

¿Cuál es el tipo de una instancia de aplicación mixin? En general, es un subtipo de su superclase, y también un subtipo del tipo denotado por el nombre del mixin en sí, es decir, el tipo de la clase original.

— dartlang.org

Entonces significa que este programa

class A {
String getMessage() => 'A';
}

class B {
String getMessage() => 'B';
}

class P {
String getMessage() => 'P';
}

class AB extends P with A, B {}

class BA extends P with B, A {}

void main() {
AB ab = AB();
print(ab is P);
print(ab is A);
print(ab is B);

BA ba = BA();
print(ba is P);
print(ba is A);
print(ba is B);
}

Puedes ejecutar este programa en DartPad

imprimirá seis líneas con true en la consola.

Explicación detallada

Lasse R. H. Nielsen me dio una gran explicación:

Ya que cada aplicación mixin crea una nueva clase, también crea una nueva interface (porque todas las clases Dart también definen interfaces). Tal como se describió, la nueva clase extiende la superclase e incluye copias de los miembros de las clases mixin, pero también implementa la interfaz de la clase mixin.

En la mayoría de los casos, no hay manera de referirse a esa clase de aplicación mixin o a su interfaz; la clase para Super with Mixin es solo una superclase anónima de la clase declarada como class C extends Super with Mixin {}. Si nombra una aplicación mixin como class CSuper = Super with Mixin {}, entonces puedes referirte a la clase de aplicación mixin y a su interfaz, y será un subtipo de ambos Super y Mixin.

— Lass R. H. Nielsen

🙄 ¿Cuándo usar mixins?

Los mixins son muy útiles cuando quieres compartir un comportamiento a travez de múltiples clases que no comparten la misma jerarquía de clase, o cuando no tiene sentido implementar tal comportamiento en una superclase.

Es el típico caso de serialización (echa un vistaso a jaguar_serializer por ejemplo) o la persistencia. Pero puedes también usar mixins para proporcionar algunas funciones de utilidad (como él RenderSliverHelpers en Flutter).

Tomate un tiempo para jugar con esta función, y estoy seguro que encontrarás nuevos casos de uso😉. No te limites a usar mixins sin estado, tú puedes absolutamente almacenar variables y usarlas!

📈 La especificación de mixin está evolucionando

Si estás interesado en la evolución del lenguaje Dart, debes saber que su especificación evolucionará en Dart 2.1 y les encantarán sus comentarios aquí y ahí. Para información detallada, puedes leer esto.

Para darte una idea del futuro, considere este código fuente:

abstract class Super {
void method() {
print("Super");
}
}

class MySuper implements Super {
void method() {
print("MySuper");
}
}

mixin Mixin on Super {
void method() {
super.method();
print("Sub");
}
}

class Client extends MySuper with Mixin {}

void main() {
Client().method();
}

La declaración de la línea 13 a la 18, indica una restricción de superclase en Super. Significa que con el fin de aplicar este mixin a una clase, esta clase debe extenderse o implementar Super porque el mixin usa una función proporcionada por Super.

La salida de este programa sería:

MySuper
Super

Si te preguntas ¿por qué?, tenga en cuenta como se linealizan los mixins:

De hecho, la llamada a super.method() en línea 15 actualmente llama al método declarado en línea 8.

🐬 Ejemplo Animal Completo

Puedes encontrar abajo, el ejemplo Animales completo con el que presente los mixins:

abstract class Animal {}

abstract class Mammal extends Animal {}

abstract class Bird extends Animal {}

abstract class Fish extends Animal {}

abstract class Walker {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory Walker._() => null;

void walk() {
print("I'm walking");
}
}

abstract class Swimmer {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory Swimmer._() => null;

void swim() {
print("I'm swimming");
}
}

abstract class Flyer {
// This class is intended to be used as a mixin, and should not be
// extended directly.
factory Flyer._() => null;

void fly() {
print("I'm flying");
}
}

class Dolphin extends Mammal with Swimmer {}

class Bat extends Mammal with Walker, Flyer {}

class Cat extends Mammal with Walker {}

class Dove extends Bird with Walker, Flyer {}

class Duck extends Bird with Walker, Swimmer, Flyer {}

class Shark extends Fish with Swimmer {}

class FlyingFish extends Fish with Swimmer, Flyer {}

Puedes ver abájo como estos mixins son linealizados:

📗 Conclusión

Viste que los mixins son un concepto poderoso que te permite reusar código en múltiples jerarquías de clase.
Flutter usa esta función mucho, y encontré esto importante para entenderlo mejor, es por eso que compartí mi comprensión contigo.

Si tienes algunas preguntas, es libre de publicarlas 👇.

Cualquier comentario es bienvenido 😃.

Si disfrutaste de esta historia, puedes apoyarme con 👏 esto.

Puedes además seguirme en Twitter

Gracias a Jay (Jeroen) por la corrección de pruebas.

--

--