mixin și with în Dart

Moștenire, mixin-uri și modificatori de clasă Dart 2.1 (mixin), Dart 3.0 (mixin class)

Un mixin este un pachet de metode și câmpuri pe care îl adaugi în mai multe clase cu with, fără moștenire.

Sintaxă
mixin Nume {
  Tip metoda() { ... }
}
mixin Nume on ClasaDeBaza { ... }   // doar pentru subclasele lui ClasaDeBaza

class C extends Parinte with Mixin1, Mixin2 { ... }
mixin class Nume { ... }            // se poate folosi și ca clasă, și ca mixin

Ce face mixin și with

Un mixin grupează cod reutilizabil (metode, getter-e, câmpuri) care nu are sens singur. Cu with îl „amesteci” într-o clasă: class Rata extends Pasare with Inotator, Zburator. Clasa primește tot codul mixin-urilor, ca și cum l-ar fi scris ea.

Spre deosebire de extends, poți folosi oricâte mixin-uri. Ele se aplică în ordine, de la stânga la dreapta; dacă două au aceeași metodă, câștigă ultimul. Un mixin nu are constructori și nu se poate crea singur (Inotator() este o eroare).

Cu mixin Nume on Baza mixin-ul poate fi folosit doar în subclase ale lui Baza și, în schimb, poate apela metodele lui Baza (inclusiv cu super). Din Dart 3, mixin class declară ceva ce poate fi folosit și ca clasă obișnuită, și ca mixin.

Exemple

Exemplul 1: Abilități combinate

Rața primește ambele abilități, pinguinul doar înotul.

mixin Inotator {
  void inoata() => print('$runtimeType înoată.');
}

mixin Zburator {
  void zboara() => print('$runtimeType zboară.');
}

class Pasare {}

class Rata extends Pasare with Inotator, Zburator {}

class Pinguin extends Pasare with Inotator {}

void main() {
  final rata = Rata();
  rata.inoata();
  rata.zboara();
  Pinguin().inoata();

  final List<Pasare> pasari = [rata, Pinguin()];
  for (final p in pasari) {
    print('${p.runtimeType} zboară? ${p is Zburator}');
  }
}

Rezultat

Rata înoată.
Rata zboară.
Pinguin înoată.
Rata zboară? true
Pinguin zboară? false

Exemplul 2: Mixin cu stare și restricție on

Jurnalizat poate fi folosit doar pe subclase ale lui Serviciu și apelează super.porneste().

class Serviciu {
  final String nume;
  Serviciu(this.nume);
  void porneste() => print('$nume a pornit.');
}

mixin Jurnalizat on Serviciu {
  final List<String> jurnal = [];

  @override
  void porneste() {
    jurnal.add('pornire $nume');
    super.porneste();
  }
}

class ServiciuPlati extends Serviciu with Jurnalizat {
  ServiciuPlati() : super('Plăți');
}

void main() {
  final s = ServiciuPlati();
  s.porneste();
  s.porneste();
  print(s.jurnal);
}

Rezultat

Plăți a pornit.
Plăți a pornit.
[pornire Plăți, pornire Plăți]

Exemplul 3: Ordinea mixin-urilor

Ambele mixin-uri au salut; fiecare cheamă super.salut(), deci se vede ordinea în care au fost aplicate.

class Baza {
  String salut() => 'Bază';
}

mixin A on Baza {
  @override
  String salut() => 'A > ${super.salut()}';
}

mixin B on Baza {
  @override
  String salut() => 'B > ${super.salut()}';
}

class AB extends Baza with A, B {}

class BA extends Baza with B, A {}

void main() {
  print(AB().salut());
  print(BA().salut());
}

Rezultat

B > A > Bază
A > B > Bază

Greșeli frecvente

  • class Pasare extends Zburator când Zburator este un mixin: eroarea Classes can only extend other classes. Folosește with.
  • Constructori într-un mixin: nu sunt permiși. Inițializează câmpurile direct sau folosește late.
  • Din Dart 3, o clasă obișnuită nu mai poate fi folosită ca mixin; dacă vrei ambele roluri, declar-o mixin class.

Comenzi înrudite

  • extends: Creează o subclasă care moștenește câmpurile și metodele altei clase și le poate înlocui.
  • implements: O clasă promite că are toți membrii altor clase (interfețe) și îi scrie singură, fără să moștenească codul lor.
  • super: Accesează clasa părinte: constructorul ei (în lista de inițializare) sau metodele pe care subclasa le-a înlocuit.
  • abstract: O clasă abstractă nu poate fi instanțiată direct; definește ce trebuie să facă subclasele ei.