Módulo 03: Dart Deep Dive

Covariance Contravariance

Sistema Tipos

# Covarianza y Contravarianza en Dart Estos términos suenan intimidantes, pero describen reglas simples sobre cómo se relacionan los tipos complejos (como Listas o Funciones) cuando sus subtipos cambian. ## Conceptos Básicos Supongamos: - `Animal` es la clase padre. - `Dog` extiende `Animal`. ### Covarianza (Covariance) "Si `Dog` es un `Animal`, entonces `List` es una `List`". La dirección de la relación se mantiene. ### Contravarianza (Contravariance) La dirección de la relación se invierte. (Común en parámetros de funciones). ### Invarianza (Invariance) No hay relación. `List` NO es `List`. --- ## Covarianza en Dart En Dart, **los tipos genéricos son covariantes** por defecto. ```dart class Animal {} class Dog extends Animal {} void main() { List dogs = [Dog()]; // ✅ Esto es válido en Dart (Covarianza) List animals = dogs; // ⚠️ PELIGRO: Runtime Error // Como 'animals' apunta a una lista de Dogs, // no puedes agregar un Cat, aunque el tipo estático sea List. animals.add(Animal()); // Error en ejecución: type 'Animal' is not a subtype of element type 'Dog' } ``` ### El problema de la Covarianza La covarianza permite flexibilidad pero introduce riesgos de seguridad de tipos en tiempo de ejecución al escribir en colecciones. Por eso, es mejor usar `List` si planeas mezclar animales. ## Contravarianza en Funciones Donde la cosa se pone interesante es en las funciones. Para que una función pueda reemplazar a otra (subtipo), debe aceptar **lo mismo o algo más genérico** (parámetros contravariantes) y devolver **lo mismo o algo más específico** (retorno covariante). **Regla:** "Sé liberal en lo que aceptas (inputs) y estricto en lo que produces (outputs)". ```dart class Animal {} class Mouse extends Animal {} typedef AnimalHandler = void Function(Mouse); void handleMouse(Mouse m) => print('Handling mouse'); void handleAnimal(Animal a) => print('Handling animal'); void main() { AnimalHandler handler; handler = handleMouse; // ✅ OK: Coincide exacto // ✅ OK: Contravarianza // Si espero una función que maneje Ratones, // una función que maneja CUALQUIER Animal me sirve. handler = handleAnimal; handler(Mouse()); // Funciona con ambas } ``` ## La palabra clave `covariant` A veces quieres romper las reglas de seguridad por conveniencia. Puedes usar `covariant` para forzar que un parámetro sea de un subtipo específico, aceptando el riesgo de error en runtime. ```dart class Animal { void chase(Animal x) {} } class Mouse extends Animal {} class Cat extends Animal { // Sobrescribimos el método. // Normalmente, el parámetro debería ser Animal (para ser seguro). // Con 'covariant', decimos: "Prometo que solo pasaré Mouse a los gatos". @override void chase(covariant Mouse x) { print('Cat chasing mouse'); } } ``` ## Uso Práctico en Flutter Esto se ve mucho en `AnimationController`. ```dart // TickerProviderStateMixin implementa TickerProvider // AnimationController espera un TickerProvider class _MyState extends State with TickerProviderStateMixin { late AnimationController _controller; @override void initState() { super.initState(); // 'this' es TickerProviderStateMixin // AnimationController acepta TickerProvider // Como TickerProviderStateMixin ES UN TickerProvider (Subtipo) // Todo funciona. _controller = AnimationController(vsync: this); } } ``` ## Resumen 1. **Variables/Retornos (Covarianza):** Puedes devolver un subtipo (`Dog`) donde se espera un supertipo (`Animal`). 2. **Parámetros (Contravarianza):** Puedes aceptar un supertipo (`Animal`) donde se espera un subtipo (`Dog`) en una función callback. 3. **Generics (Covarianza):** `List` es subtipo de `List` en Dart (cuidado al escribir). 4. **`covariant`:** Úsalo para estrechar tipos de parámetros en herencia, sacrificando seguridad estática.

Este apartado profundiza en los conceptos clave, proporcionando ejemplos prácticos y mejores prácticas para su aplicación en proyectos reales.

Al comprender estos detalles, podrás diseñar soluciones más robustas, mantenibles y escalables en tus aplicaciones Flutter y Dart.