Skip to Content
FlutterLezione 2 Widget Di Base2.5 - Approfondimenti su Dart

2.5 - Approfondimenti su Dart

1. Costruzione di Classi, Ereditarietà e Mixin

Dart è un linguaggio orientato agli oggetti. Qui vediamo come definire una classe, un costruttore e come implementare l’ereditarietà e i mixin.

Esempio: Classe e Costruttore

class Persona { //Attributi final String nome; // Questo attributo è dichiarato immutabile int eta; // Esempi di costruttori // Costruttore shorthand Persona(this.nome, this.eta); /* * Questo però richiede che gli attributi siano nullable (Non usato in dart) Persona(nome,eta){ this.nome = nome; this.eta = eta; } */ /* Costruttore con valori posizionali e valori nominali Persona(this.eta,{required this.nome}); */ //Metodi void saluta() { print("Ciao, sono $nome e ho $eta anni."); } int getAge() { return eta; } } void main() { var p = Persona("Alice", 28); p.eta = 27; // p.nome = "Anna"; // Questa riga produce un errore. p.saluta(); }
Note

Per creare un istanza di una classe come avete notato non è necessario utilizzare la keyword new questo semplifica la generazione di layout visto che ogni widget che noi usiamo è un istanza di una classe

Visibilità degli attributi in Dart

In Dart, il controllo dell’accesso a variabili e metodi si basa su una convenzione semplice e precisa: l’uso dell’underscore _.

Pubblico

Per impostazione predefinita, tutti i campi e metodi in Dart sono pubblici. Ciò significa che sono accessibili da qualsiasi punto dell’app, da altri file, classi o package.

Privato con _

In Dart non esiste la parola chiave private. Per rendere un membro privato, si antepone un underscore _ al suo nome.

Questo rende il membro privato al file, cioè visibile solo dentro lo stesso file Dart.

class Persona { String _segreto = '1234'; } void main() { final p = Persona(); print(p._segreto); // ❌ Errore se questo codice è in un altro file }

Dart è progettato per essere semplice, leggibile e facilmente integrabile con altri ambienti, incluso JavaScript (in quanto può essere compilato in JS).

A differenza di Java o C++, Dart non punta su un sistema complesso di visibilità (private, protected, internal, ecc.)

Questo incoraggia l’organizzazione del codice per file: se vuoi nascondere un dettaglio, mettilo nello stesso file e rendilo _privato.

Getter e Setter in Dart

In Dart, puoi accedere e modificare campi privati in modo controllato usando getter e setter.


Perché usarli?

  • Per nascondere l’implementazione interna
  • Per controllare/modificare i dati quando vengono letti o scritti
  • Per proteggere campi privati (_variabile) mantenendo l’accesso pubblico

Sintassi di base

class Conto { double _saldo = 0; double get saldo => _saldo; set saldo(double valore) { if (valore >= 0) { _saldo = valore; } } }

Utilizzo

void main() { final conto = Conto(); conto.saldo = 100.0; // usa il setter print(conto.saldo); // usa il getter }

Nota su prestazioni e semplicità

  • Getter e setter in Dart non usano parentesi tonde quando li chiami.
  • Questo li fa sembrare variabili, ma in realtà sono metodi.
persona.nome = 'Mario'; // chiama il setter print(persona.nome); // chiama il getter

🧠 Quiz:

Rispondi alle seguenti domande per verificare quanto hai capito.


Cosa significa quando un campo in Dart inizia con _?

Il seguente getter in Dart è valido? String get nome => _nome;

Cosa succede se accedi a _password da un file diverso da quello in cui è definito?


Variabili static

In Dart, la parola chiave static serve per dichiarare variabili o metodi che appartengono alla classe, non alle singole istanze.


Cos’è una variabile static?

  • Una variabile static è condivisa da tutte le istanze della classe.
  • Non serve creare un oggetto per accedervi.
  • Si accede con NomeClasse.nomeVariabile.

Sintassi base

class Contatore { static int valore = 0; } void main() { print(Contatore.valore); // ➜ 0 Contatore.valore++; print(Contatore.valore); // ➜ 1 }

Esempio con istanze

class Persona { static int numeroTotale = 0; String nome; Persona(this.nome) { numeroTotale++; } } void main() { var p1 = Persona('Luca'); var p2 = Persona('Anna'); print(Persona.numeroTotale); // ➜ 2 }

La variabile numeroTotale è incrementata da ogni oggetto, ma è unica per la classe.


Metodi statici

Anche i metodi possono essere statici. Servono per funzioni utilitarie o di calcolo:

class Matematica { static int quadrato(int x) => x * x; } void main() { print(Matematica.quadrato(4)); // ➜ 16 }

Attenzione

  • I metodi static non possono accedere a campi non statici della classe.
class Test { int numero = 5; static void stampa() { // print(numero); ❌ Errore! } }
Note

Dart non ha classi static come in Java o C#.

Dart ha un approccio semplice e flessibile:

  • Se una classe non ha stato interno, non serve istanziarla
  • Se non vuoi che venga istanziata, nascondi il costruttore

Il costruttore factory

In Dart, il costruttore factory ti permette di personalizzare la creazione di un oggetto.

A differenza del costruttore classico, può:

  • Restituire un’istanza già esistente
  • Applicare logica condizionale o modificare l’input
  • Usare costruttori privati per creare oggetti interni alla classe

Sintassi base

class Persona { final String nome; // Costruttore privato Persona._(this.nome); // Costruttore factory factory Persona(String nome) { return Persona._(nome.toUpperCase()); } } void main() { final p = Persona('mario'); print(p.nome); // ➜ 'MARIO' }

Perché usare factory?

  • Per controllare come e quando viene creato l’oggetto
  • Per applicare validazioni o trasformazioni
  • Per implementare il pattern Singleton
  • Per riutilizzare istanze già esistenti

Confronto: factory vs costruttore classico

CaratteristicaCostruttore classicofactory constructor
Crea sempre una nuova istanza❌ può restituire oggetto esistente
Può contenere logica condizionale
Può restituire valori diversi
Può accedere a costruttori privati
Utile per Singleton/cache

Esempio: Singleton con factory

class Config { static final Config _istanza = Config._(); Config._(); factory Config() => _istanza; } void main() { var a = Config(); var b = Config(); print(identical(a, b)); // true }

Esempio: validazione in factory

class CodiceFiscale { final String codice; factory CodiceFiscale(String codice) { if (codice.length != 16) { throw FormatException('Codice non valido'); } return CodiceFiscale._(codice.toUpperCase()); } CodiceFiscale._(this.codice); }

Ereditarietà

L’ereditarietà permette a una classe di riutilizzare codice da un’altra classe, estendendola e personalizzandola.

Uso di super nel costruttore

class Persona { String nome; int eta; Persona(this.nome, this.eta); } class Studente extends Persona { String corso; Studente(String nome, int eta, this.corso) : super(nome, eta); //chiama il costruttore della superclasse `Persona` }

Override dei metodi

Puoi sovrascrivere un metodo della superclasse usando @override.

class Persona { String nome; int eta; Persona(this.nome, this.eta); void saluta() => print("Ciao, sono $nome e ho $eta anni."); } class Studente extends Persona { String corso; Studente(String nome, int eta, this.corso) : super(nome, eta); @override void saluta() { print("Ciao, sono $nome, ho $eta anni e studio $corso."); } }

Polimorfismo

Puoi usare una variabile di tipo Persona per contenere un Studente.

void main() { Persona p = Studente("Marco", 22, "Informatica"); p.saluta(); // ➜ Chiama comunque il metodo sovrascritto! }

polimorfismo: il comportamento si adatta al tipo reale dell’oggetto.


Casting: is, as, is!

Controllo del tipo con is
if (p is Studente) { print("È uno studente."); }
Cast esplicito con as
if (p is Studente) { Studente s = p as Studente; print("Corso: ${s.corso}"); }
Negazione con is!
if (p is! Studente) { print("Non è uno studente."); }

Esempio completo

class Persona { String nome; int eta; Persona(this.nome, this.eta); void saluta() => print("Ciao, sono $nome e ho $eta anni."); } class Studente extends Persona { String corso; Studente(String nome, int eta, this.corso) : super(nome, eta); @override void saluta() { print("Ciao, sono $nome, ho $eta anni e studio $corso."); } } void main() { Persona p = Studente("Marco", 22, "Informatica"); p.saluta(); // ➜ Chiama metodo di Studente if (p is Studente) { Studente s = p as Studente; print("Corso: ${s.corso}"); } }

implements: usare una classe come interfaccia

In Dart, non esiste la parola chiave interface.
Tuttavia, qualsiasi classe può essere usata come interfaccia grazie alla parola chiave implements.

Quando una classe implements un’altra:

  • Non eredita l’implementazione
  • Deve riscrivere tutti i metodi e le proprietà
  • Agisce come se stesse soddisfacendo un contratto

Esempio pratico
class Stampabile { void stampa() { print('Stampo...'); } } class Fattura implements Stampabile { @override void stampa() { print('Stampo la fattura'); } }
Differenza tra extends e implements
Cosa fa?extendsimplements
Eredita implementazione✅ sì❌ no, devi riscrivere tutto
Richiede override❌ no✅ sì
Serve per interfacce❌ no (ma possibile)✅ sì
Implementazione multipla

Puoi implementare più classi/interfacce:

class Salva { void salva() {} } class Invia { void invia() {} } class Report implements Salva, Invia { @override void salva() => print('Report salvato'); @override void invia() => print('Report inviato'); }

Le classi abstract

In Dart puoi definire una classe astratta usando la keyword abstract.
Una classe abstract serve come base per altre classi, ma non può essere istanziata direttamente.

A cosa serve una classe astratta?

  • A definire metodi da implementare obbligatoriamente
  • A fornire funzionalità condivise per più sottoclassi
  • A definire un modello comune (contratto parziale)

Sintassi base

abstract class Animale { void parla(); // metodo astratto } void main() { // var a = Animale(); ❌ Errore: una classe astratta non può essere istanziata }

Può avere anche metodi normali

Una abstract class può anche contenere metodi con corpo:

abstract class Forma { void disegna(); // metodo astratto void descrizione() { print("Sono una forma geometrica."); } }

Confronto: abstract vs implements

Caratteristicaabstract classimplements
Può avere codice✅ sì❌ no
Può essere estesa (extends)✅ sì❌ no
Obbligo di override totale❌ solo se il metodo è astratto✅ sempre
Esempio d’uso tipicoBase comune con logica condivisaInterfaccia pura

Mixins

Un mixin è un modo per riutilizzare codice tra più classi, senza dover usare l’ereditarietà classica (extends) o le interfacce (implements).

A cosa servono i mixin?

  • Per aggiungere comportamenti comuni a più classi
  • Per evitare la duplicazione di codice
  • Per non usare ereditarietà multipla, che Dart non supporta

Sintassi base

Per creare un mixin:

mixin Volante { void vola() => print("Sto volando!"); }

Per usarlo in una classe:

class Uccello with Volante {}

Esempio pratico

mixin Nuotante { void nuota() => print("Sto nuotando!"); } mixin Volante { void vola() => print("Sto volando!"); } class Anatra with Nuotante, Volante {} void main() { var a = Anatra(); a.nuota(); // ➜ Sto nuotando! a.vola(); // ➜ Sto volando! }

Limitazioni: on

Se vuoi limitare l’uso di un mixin solo a classi che estendono un certo tipo, puoi usare la keyword on.

class Animale {} mixin Coda on Animale { void scodinzola() => print("Scodinzolo!"); } class Cane extends Animale with Coda {} // Ok class Robot with Coda {} // ❌ Errore: Robot non è un Animale

Mixin con stato

Un mixin può avere variabili, ma deve essere usato con attenzione:

mixin Logger { void log(String msg) { final now = DateTime.now(); print("[$now] $msg"); } } class Servizio with Logger { void esegui() { log("Servizio eseguito"); } }

Questo perché i mixin vengono iniettati nella classe, e ogni classe ne ottiene una copia separata, il che può generare:

  • Conflitti di nome tra mixin
  • Stato duplicato non intenzionale
  • Confusione nella gestione del comportamento

Conflitti di nome tra mixin
mixin A { String status = 'da A'; } mixin B { String status = 'da B'; // ❌ Dart non sa quale usare } class C with A, B {} // Errore di ambiguità

Uso corretto dei mixin

Lo stato dovrebbe stare nella classe che usa il mixin.

mixin Logger { void log(String msg) { print("LOG: $msg"); } } class Servizio with Logger { int operazioni = 0; void esegui() { operazioni++; log("Operazione n° $operazioni"); } }

📌 In questo modo, Logger è riutilizzabile e non introduce stato.


sealed in Dart

La keyword sealed permette di creare classi con gerarchie chiuse.
Significa che solo le classi nello stesso file possono estendere o implementare una classe sealed.


A cosa serve?

  • Definire un insieme limitato e controllato di sottotipi
  • Forzare la gestione di tutti i casi possibili in switch o if
  • Impedire l’estensione da parte di altri file o moduli

Sintassi di base

sealed class StatoConnessione {} class Connesso extends StatoConnessione {} class Disconnesso extends StatoConnessione {} class InCaricamento extends StatoConnessione {}

Solo queste classi possono estendere StatoConnessione perché si trovano nello stesso file.


Uso pratico con switch

void gestisci(StatoConnessione stato) { switch (stato) { case Connesso(): print("Utente connesso"); case Disconnesso(): print("Utente disconnesso"); case InCaricamento(): print("Caricamento..."); } }
⚠️
Warning

Il compilatore sa tutti i sottotipi possibili e ti avvisa se ne dimentichi uno.

Differenza tra abstract e sealed

ModificatoreIstanziabile?Estendibile da altri file?Finalità
abstract❌ No✅ SìModello base, libero
sealed❌ No❌ Solo nello stesso fileInsieme chiuso di sottotipi

Quando usarlo?

  • Quando vuoi creare uno stato o evento chiuso
  • Quando vuoi forzare la gestione completa in un switch
  • Quando vuoi evitare estensioni non desiderate

Nota sulle classi avanzate in Dart

Dart offre diverse keyword per gestire in modo preciso l’estensione, l’implementazione e l’uso delle classi:
abstract, sealed, base, final, e interface.

Queste parole chiave permettono di:

  • Creare classi che non possono essere estese (final)
  • Definire contratti da implementare (interface)
  • Limitare l’estensione a un solo file (sealed)
  • Controllare gerarchie (base)
  • Definire modelli astratti (abstract)

Tuttavia, sarà utile approfondire in futuro ma non sono necessarie queste informazioni adesso.