covariant în Dart

Permite unei metode suprascrise să ceară un parametru de un tip mai restrâns decât în clasa părinte.

Sintaxă
class Parinte {
  void metoda(covariant TipLarg x) { ... }
}
class Copil extends Parinte {
  @override
  void metoda(TipIngust x) { ... }   // permis datorită lui covariant
}

Ce face covariant

În mod normal, o metodă suprascrisă trebuie să accepte cel puțin aceleași argumente ca originalul. Dacă Animal.mananca(Hrana h), atunci Pisica.mananca(Peste p) ar fi o eroare, pentru că cineva ar putea da pisicii orice Hrana.

Cu covariant (pus pe parametru, în părinte sau în copil), îi spui analizorului: „știu ce fac, verifică tipul la rulare”. Codul se compilează, iar dacă la rulare primește alt tip, apare un TypeError.

Se folosește rar, în ierarhii unde tipul exact al parametrului depinde de subclasă (de exemplu operator == sau metode de comparare). Când se poate, generics sunt o soluție mai sigură.

Exemple

Exemplul 1: Fiecare animal mănâncă alt fel de hrană

covariant din clasa Animal permite subclaselor să ceară un tip mai precis de hrană.

class Hrana {
  final String nume;
  Hrana(this.nume);
}

class Peste extends Hrana {
  Peste() : super('pește');
}

class Os extends Hrana {
  Os() : super('os');
}

class Animal {
  void mananca(covariant Hrana h) => print('Animalul mănâncă ${h.nume}.');
}

class Pisica extends Animal {
  @override
  void mananca(Peste p) => print('Pisica mănâncă ${p.nume}.');
}

class Caine extends Animal {
  @override
  void mananca(Os o) => print('Câinele roade un ${o.nume}.');
}

void main() {
  Pisica().mananca(Peste());
  Caine().mananca(Os());
}

Rezultat

Pisica mănâncă pește.
Câinele roade un os.

Exemplul 2: Verificarea de la rulare

Văzută ca Animal, pisica primește un os. Analizorul permite, dar la rulare apare TypeError.

class Hrana {}

class Peste extends Hrana {}

class Os extends Hrana {}

class Animal {
  void mananca(covariant Hrana h) => print('mănâncă');
}

class Pisica extends Animal {
  @override
  void mananca(Peste p) => print('Pisica mănâncă pește.');
}

void main() {
  Animal a = Pisica();
  a.mananca(Peste());
  try {
    a.mananca(Os());
  } on TypeError {
    print('Eroare de tip: pisica nu mănâncă oase.');
  }
}

Rezultat

Pisica mănâncă pește.
Eroare de tip: pisica nu mănâncă oase.

Greșeli frecvente

  • Restrângerea tipului unui parametru fără covariant: eroarea 'Pisica.mananca' ('void Function(Peste)') isn't a valid override of 'Animal.mananca' ('void Function(Hrana)').
  • Să folosești covariant ca să „scapi” de erori de tip: mută doar verificarea la rulare, unde greșeala costă mai mult.
  • Să uiți că o colecție de tipul părinte poate primi orice subtip: fiecare apel prin tipul părinte poate eșua la rulare.

Comenzi înrudite

  • extends: Creează o subclasă care moștenește câmpurile și metodele altei clase și le poate înlocui.
  • generics: Clase și funcții cu tipuri ca parametri (<T>), care funcționează la fel pentru orice tip, păstrând verificarea tipurilor.
  • is și is!: Verifică la rulare dacă o valoare are un anumit tip; după verificare, variabila poate fi folosită ca acel tip.