# 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.