factory în Dart

Constructor care nu creează neapărat un obiect nou: poate întoarce unul din cache sau o subclasă.

Sintaxă
class Nume {
  factory Nume(...) {
    ...
    return obiect;      // un obiect existent sau unul nou (chiar și al unei subclase)
  }
  factory Nume.altNume(...) => ...;
}

Ce face factory

Un constructor obișnuit creează mereu un obiect nou al clasei lui. Un constructor factory este mai liber: are un corp ca o funcție și trebuie să întoarcă un obiect, care poate fi unul deja existent (dintr-un cache), o instanță a unei subclase sau rezultatul altui constructor.

Pentru cine îl apelează, arată exact ca un constructor: Moneda('RON'). Este folosit des pentru fromJson, pentru a verifica datele înainte de creare sau pentru a alege implementarea potrivită.

Într-un constructor factory nu ai acces la this (obiectul nu a fost creat) și nu poți folosi lista de inițializare sau parametri this.camp.

Exemple

Exemplul 1: Cache: același obiect pentru același cod

A doua cerere pentru „RON” întoarce obiectul creat la prima, nu unul nou.

class Moneda {
  final String cod;
  static final Map<String, Moneda> _cache = {};

  Moneda._intern(this.cod) {
    print('Creez moneda $cod');
  }

  factory Moneda(String cod) => _cache.putIfAbsent(cod, () => Moneda._intern(cod));
}

void main() {
  final a = Moneda('RON');
  final b = Moneda('EUR');
  final c = Moneda('RON');
  print(identical(a, c));
  print(identical(a, b));
}

Rezultat

Creez moneda RON
Creez moneda EUR
true
false

Exemplul 2: Alegerea subclasei potrivite

Forma.din primește un text și întoarce un Cerc sau un Dreptunghi.

abstract class Forma {
  double get arie;

  factory Forma.din(String descriere) {
    final parti = descriere.split(' ');
    final valori = parti.skip(1).map(double.parse).toList();
    return switch (parti.first) {
      'cerc' => Cerc(valori[0]),
      'dreptunghi' => Dreptunghi(valori[0], valori[1]),
      _ => throw ArgumentError('formă necunoscută: ${parti.first}'),
    };
  }
}

class Cerc implements Forma {
  final double raza;
  Cerc(this.raza);
  @override
  double get arie => 3.14159 * raza * raza;
}

class Dreptunghi implements Forma {
  final double l, h;
  Dreptunghi(this.l, this.h);
  @override
  double get arie => l * h;
}

void main() {
  for (final text in ['cerc 2', 'dreptunghi 3 4.5']) {
    final f = Forma.din(text);
    print('${f.runtimeType}: arie ${f.arie.toStringAsFixed(2)}');
  }
}

Rezultat

Cerc: arie 12.57
Dreptunghi: arie 13.50

Exemplul 3: Validare înainte de creare

factory Email curăță textul și aruncă o excepție dacă nu este o adresă validă.

class Email {
  final String adresa;
  Email._(this.adresa);

  factory Email(String text) {
    final curat = text.trim().toLowerCase();
    if (!curat.contains('@')) {
      throw FormatException('adresă invalidă', text);
    }
    return Email._(curat);
  }
}

void main() {
  print(Email('  Ana.Pop@Exemplu.RO ').adresa);
  try {
    Email('ana.exemplu.ro');
  } on FormatException catch (e) {
    print('Eroare: ${e.message} (${e.source})');
  }
}

Rezultat

ana.pop@exemplu.ro
Eroare: adresă invalidă (ana.exemplu.ro)

Greșeli frecvente

  • Folosirea lui this într-un constructor factory: obiectul nu există încă.
  • Uitarea lui return: un constructor factory trebuie să întoarcă un obiect.
  • factory când un constructor numit obișnuit ar fi suficient: folosește-l doar când ai nevoie de cache, subclase sau logică înainte de creare.

Comenzi înrudite

  • constructori: Funcțiile speciale care creează și inițializează obiectele unei clase.
  • constructori numiți: Constructori cu nume propriu (Clasa.nume), pentru mai multe moduri de a crea același tip de obiect.
  • static: Membri care aparțin clasei, nu fiecărui obiect: se folosesc prin numele clasei, ca Nume.camp.
  • abstract: O clasă abstractă nu poate fi instanțiată direct; definește ce trebuie să facă subclasele ei.