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