44 OOP6 – classi con ES6
Con ECMAScript 6 abbiamo la possibilità di analizzare ciò che di nuovo, a livello sintattico, ci offre per la definizione delle classi. Quello che andremo a vedere a livello linguistico assomiglia molto ad altri linguaggi, ma quello che avviene come motore è una conversione che punta nella direzione dei costrutti visti in precedenza. Abbiamo finalmente una sintassi che racchiude interamente il concetto di stato interno, cioè delle proprietà e dei metodi che descrivono una certa istanza. Questo può essere eseguito tramite la keyword class:
class Persona { }
Tramite questa definizione di classe, anche se vuoto, ho già la possibilità di creare oggetti
let Fantozzi = new Persona();
interessante se andiamo ad investigare sul tipo di istanza creata e del suo generatore
writeln(Fantozzi);
writeln(Persona);
Nel primo caso mi restituirà con poca sorpresa che è un object.
Nel secondo, rifacendosi ai meccanismi espletati nelle lezioni precedenti, restituirà function, in pratica siamo in presenza di una funzione costruttore mascherata da classe.
Nelle classi, non posso aggiungere delle proprietà direttamente con let (o var) come per altri linguaggi OOP, ma devo usare una funzione speciale costruttore, tramite la keyword dedicata constructor()
class Persona { constructor(cognome, nome) { this.cognome = cognome; this.nome = nome; } }
Ovviamente posso passare dei parametri in ingresso e torniamo successivamente all’uso del this.
Non sarà poi poi obbligatorio utilizzare questo costruttore perché rimarrà sempre disponibile quello di default senza parametri, fornito automaticamente dall’interprete.
let Fantozzi = new Persona(); writeln(Fantozzi.nome);
l’oggetto Fantozzi non creerà errori a runtime, ma se tenteremo di accedere e stampare il nome, si otterrà un undefined.
Un buon compromesso è quello di utilizzare i valori di default nel costruttore
constructor(cognome="sconosciuto", nome="sconosciuto") { this.cognome = cognome; this.nome = nome; } let Fantozzi = new Persona(); writeln(Fantozzi.nome);
Salta subito all’occhio che le variabili dello stato interno non sono private, posso accedervi dall’esterno a piacimento. Ogni classe può avere un solo costruttore.
Ogni istanza mantiene la propria copia delle proprietà:
let Fantozzi = new Persona("Fantozzi", "Ugo"); writeln(Fantozzi.nome); let Filini = new Persona("Filini", "Silvio"); writeln(Filini.nome); writeln(Fantozzi.nome);
è molto semplice ed elegante dichiarare dei metodi, ritorna la nostra etichetta
class Persona { constructor(cognome="sconosciuto", nome="sconosciuto") { this.cognome = cognome; this.nome = nome; } etichetta() { return `Egr. Sig. ${this.nome} ${this.cognome}`; } }
da notare che non bisogna usare function davanti. Il metodo definito in questo modo, crea come prima la propria copia distinta nelle classi in cui è implementato.
writeln(Filini.etichetta() ); writeln( Fantozzi.etichetta===Filini.Etichetta);
il confronto tra le 2 etichette genererà FALSE, 2 copie differenti per i 2 oggetti.
Possiamo invece trasformare etichetta in un metodo statico unico per tutti gli oggetti creati con quella classe, aggiungendo static davanti al nome del metodo
static etichetta() { return `Egr. Sig. ${this.nome} ${this.cognome}`; }
Il metodo statico può tranquillamente coesistere con i metodi condivisi, persino con lo stesso nome
class Persona { constructor(cognome="sconosciuto", nome="sconosciuto") { this.cognome = cognome; this.nome = nome; Persona.conta ++; } static etichetta() { return `Egr. Sig. ${this.nome} ${this.cognome}`; } etichetta() { return `Egr. Sig. ${this.nome} ${this.cognome}`; } }
a questo punto se richiamo il metodo dell’oggetto
writeln("Metodo di istanza: " + Filini.etichetta() );
ottengo quello condiviso e copiato unico per quell’istanza, per richiamare il metodo statico, dovrò farlo tramite il nome della classe
writeln("Metodo di classe: " + Persona.etichetta() );
Giustamente sarebbe insensato che etichetta statica stampasse il nome e cognome di filini su un metodo unico.
Proviamo come esercizio didattico ad aggiungere il conteggio del numero di persone aggiunte con la classe. Aggiungo (fuori dalla classe per motivi tecnici di js) una variabile di classe conta
Persona.conta =0;
Nei metodi della classe è possibile far riferimento a quella variabile
constructor(cognome="sconosciuto", nome="sconosciuto") { this.cognome = cognome; this.nome = nome; Persona.conta ++; }
Ogni volta che creeremo un oggetto utilizzando il constructor, la variabile conta sarà incrementata di 1. Il metodo statico personeCreate restituisce il valore della variabile
static personeCreate() { return Persona.conta;}
Dopo aver creato gli oggetti, posso stampare il valore delle persone create
writeln(Persona.conta);
oppure utilizzando una logica più sofisticata tramite il metodo statico che ci potrebbe permettere di formattare, creare cornici…
writeln(Persona.personeCreate() );
come esistono funzioni espressione, esistono anche classi espressione, quindi assegnabili ad una variabile
let Punto = class { constructor(x,y) {this.x =x; this.y=y;} }
tramite la variabile Punto sarà possibile col new creare oggetti di quella classe
let p = new Punto(10,-30); writeln(p.y);
Da notare come non sia obbligatoria specificare un nome per la classe e quindi utilizzare una classe anonima.
Specificando invece un nome è possibile richiamare la classe col suo nome per creare un oggetto al suo interno
let Punto = class Nome { constructor(x,y) {this.x =x; this.y=y;} return new p(); }
Il nome della classe non sarà visibile all’esterno, ma solo ad uso interno della classe
let Punto = new Nome //FORMA SCORRETTA E VIETATA