45 OOP6 – classi 2 setter e getter

Nella lezione precedente abbiamo visto le limitazioni attuali di ES6 nel controllo del livello di visibilità delle variabili all’interno di una classe

let Punto = class {
  constructor(x,y)
  {this.x =x; this.y=y;}
}

In particolare come sarebbe utile dichiarare le variabili x e y come private, in modo da poterne controllare meglio l’utilizzo, infatti utilizzando la classe espressione

let p = new Punto(10,-30);

nulla vieta di assegnare ad x un valore senza senso al di fuori della classe

p.x = "paperino";

Una soluzione per ovviare il problema ci è fornito dal meccanismo dei setter e getter

class PuntoIQ {
  constructor(x,y)
  {
    if (x<0 || y<0) throw "Non nel primo quadrante";

    this._x =x; this._y=y;}

    get x() {return this._x;}
    
    set x(valore) {
      if ( typeof(valore) === "boolean" || isNaN(valore) )
        throw "Non è un numero";

      if(valore<0)
        throw "Non nel primo quadrante";

      this._x = valore;
      }
  }

data la classe PuntoIQ le coordinate x, y devono essere obbligatoriamente numeri positivi.

Nel constructor troviamo un controllo che dice: se una delle 2 variabili è minore di zero sollevo un’eccezione throw

if (x<0 || y<0) throw "Non nel primo quadrante";

questo significa che possiamo eseguire la creazione dell’ oggetto all’interno di un blocco try catch con l’idea di TENTARE di creare un punto

try {
  let p = new PuntoIQ(10,-30);
 }
catch (eccezione)
{
  alert(eccezione);
}

In questo caso è palese che il -30 è negativo , ma noi potremmo avere svariati calcoli ed espressioni e qualora il risultato sia negativo il costruttore se ne accorge e solleva l’eccezione (throw) che verrà da noi catturata con il catch per restituire un popup di errore tramite alert.

Supponiamo invece che i valori siano maggiori o uguali a zero ecco la differenza:

this._x =x; this._y=y;}

get x() { return this._x; }

set x(valore) { this._x = valore; }

Le reali variabili sono contenute nelle variabili _x e _y (underline o underscore è un esempio), l’idea è quella di usare un identificatore particolare ed INVOGLIARE l’utente di questa classe (non è possibile costringere) ad utilizzare un metodo getter (get) che è in pratica una funzione

get x() { return this._x; }

quando vuole recuperare il valore di _x, e un’altra funzione setter (set) quando vuole impostare o settare il valore della medesima variabile

set x(valore) { this._x = valore; }

Nell’esempio quando qualcuno chiede il valore di x viene restituito col getter il valore di _x e quando si vuole impostare il valore di x il setter imposta il valore di _x con this._x = valore; , fermo restando che in questo caso si potranno aggiungere dei controlli.

La particolarità è che seppur il getter e il setter sono funzioni

get x()

io posso richiamarne il valore come fosse una proprietà

p.x = 56;

il tutto come se x fosse la variabile di stato interno che sto richiamando, sostituendo il valore nel setter:

set x(56) { this._x = 56; }

Ovviamente l’utente potrebbe assegnare il valore ad _x

p._x = 56;

Sfortunatamente non si può eccepire dal meccanismo (ed è per questo che si utilizzano nomi meno user frindly), ma è sconsigliato, perché su _x non si può fare controlli, infatti utilizzando il setter posso andare a fare tutti i controlli del caso, ottenendo una classe più solida, eccone un esempio conto stesso setter ampliato:

set x(valore) {
  if ( typeof(valore) === "boolean" || isNaN(valore) )
    throw "Non è un numero";

  if(valore<0)
    throw "Non primo quadrante";

  this._x = valore;
  }

il primo if controlla se è un numero

if ( typeof(valore) === "boolean" || isNaN(valore) )

se il tipo di valore è un booleano, ovvero un numero (TRUE=1, FALSE=0), oppure || isNan (is not a number) è un qualsiasi altro valore diverso da un numero solleviamo un’eccezione THROW

throw "Non è un numero";

ora che siamo certi che sia un numero verifichiamo che sia nel primo quadrante (maggiore di zero), altrimenti solleviamo un’altra eccezione

if(valore<0)
throw "Non primo quadrante";

Superati tutti i controlli impostiamo il valore della variabile più protetta _x (che poi è x)

this._x = valore;

infatti se non assegno un numero

p.x = "paperino";

restituisce il messaggio Non è un numero, analogamente se setto un numero negativo scatterà l’altra eccezione, Non primo quadrante

p.x = -12;

anche nel getter possiamo impostare dei controlli, per esempio volessimo tenere traccia di chi ha effettuato un’autenticazione, il log di un form….

Se la logica dei controlli fosse lasciata al buon cuore dei programmatori che diventeranno utenti di questa classe, qualcuno immancabilmente non farà i dovuti controlli. Se invece, commentando dovutamente le nostre classi, gli utilizzatori passeranno dai setter e getter, avremo una migliore robustezza nel codice.