Forse l'ho già detto qualche volta ma nel caso fosse sfuggito, lavoro con Ember.js, un framework Javascript per costruire SPA (Single Page Application). Al di la delle simpatie o dei pro e contro che esso può offrire, oggi vorrei illustrare un trucchetto qualora si voglia avere delle variabili private.
Prenderemo ad esempio un servizio, ma la logica si può applicare ad ogni oggetto Ember, sia esso un controller o quant'altro.
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
});
Abbiamo chiamato il nostro servizio EsempioService
ricordando brevemente che il suffisso Service
viene dalla convenzione dei nomi richiesta da Ember. Per ora il servizio è vuoto, non vi è alcun codice all'intenro, mentre abbiamo dichiarato al di fuori dell'oggetto la variabile che vogliamo mantenere privata, dandole il nome di _variabilePrivata
(con molta fantasia).
Per mostrare la differenza tra una variabile privata, ed una pubblica, aggiungiamo al servizio quest'ultima:
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
// Proprietà che vogliamo sia pubblica
proprietaPubblica: null,
});
Le variabili all'interno di un qualsiasi oggetto Ember prendono il nome di property (proprietà).
Per leggere il valore di una proprietà all'interno di un oggetto Ember, si usa la sintassi this.get('nomeProprieta')
, dove this
è l'oggetto su cui si richiama la funzione get
(nel nostro caso il servizio) mentre come parametro della funzione, il nome della proprietà.
Nel nostro caso, creiamo una funzione fittizia e leggiamo il valore della prioprietà proprietaPubblica
.
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
// Proprietà che vogliamo sia pubblica
proprietaPubblica: null,
leggiValoreProprietaPubblica() {
return this.get('proprietaPubblica');
},
});
Chiamando la funzione leggiValoreProprietaPubblica
ci verrà restituito il valore della proprietaPubblica
dichiarata poco sopra. La cosa interessante è che avendo un'istanza di questo servizio altrove, possiamo richiamare la funzione sull'oggetto servizio ed otterremo il valore della proprietà, per questo motivo l'abbiamo definita pubblica, perchè accessibile anche dall'esterno.
Supponiamo infatti di avere altrove tale proprietà:
istanzaDiEsempioDiService: Ember.inject.service('esempioDiService'),
chiamando la seguente istruzione, otterremo il valore di proprietaPubblica
anche al di fuori del servizio:
istanzaDiEsempioDiService.leggiValoreProprietaPubblica();
o alternativamente ed equivalentemente:
istanzaDiEsempioDiService.get('proprietaPubblica');
in entrambi i casi, come detto, ci verrà restutito il valore di proprietaPubblica
la quale si trova all'interno del servizio.
Lo stesso esempio può essere applicato per l'assegnamento anzichè per la sola lettura: la proprietà potrebbe essere inizializzata ad un altro valore anche dall'esterno.
E se volessimo che questa variabile/proprietà non sia accessibile dall'esterno in scrittura?
Un po' di magia
Qualora avessimo bisogno di settare _variabilePrivata
all'interno del servizio, ma volessimo mantenere il suo valore senza rendere possibile il set dall'esterno dell'oggetto, esiste un trucchetto molto interessante a cui possiamo affidarci, ma andiamo con ordine.
Aggiungiamo al nostro servizio una funzione che setta il valore di _variabilePrivata
:
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
// Proprietà che vogliamo sia pubblica
proprietaPubblica: null,
leggiValoreProprietaPubblica() {
return this.get('proprietaPubblica');
},
impostaVariabilePrivata(valore) {
_variabilePrivata = valore;
},
});
Si noti che, contrariamente a quanto si può fare con proprietaPubblica
, non possiamo impostare il valore di _variabilePrivata
usando la funzione set
di Ember (es: this.set('proprieta', valore)
) perchè essa non è una proprietà di un oggetto Ember dato che l'abbiamo dichiarata fuori!
Per settare il suo valore usiamo il semplice assegnamento con =
.
Al momento quindi, chiamando la funzione impostaVariabilePrivata
all'interno del servizio, possiamo settare il valore di _variabilePrivata
. In egual modo, sempre e solo all'interno del servizio, possiamo leggere il suo valore.
Ma a questo punto, se non fossimo almeno in grado di leggere il suo valore dall'esterno, essa servirebbe a poco. Qui entra in gioco il nostro trucchetto: per leggere la proprietà privata, aggiungiamo una computed property (letteralmente proprietà computata, rimando alla documentazione di Ember qualora non si conosca cosa sia) di sola lettura:
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
// Proprietà che vogliamo sia pubblica
proprietaPubblica: null,
valoreVariabilePrivata: Ember.computed(function() {
return _variabilePrivata;
}).readOnly(),
leggiValoreProprietaPubblica() {
return this.get('proprietaPubblica');
},
impostaVariabilePrivata(valore) {
_variabilePrivata = valore;
},
});
Adesso, chiamando this.get('valoreVariabilePrivata')
dall'interno del servizio, siamo in grado di leggere il valore di _variabilePrivata
, chiamando dall'esterno del servizio istanzaDiEsempioDiService.get('valoreVariabilePrivata')
otterremo lo stesso risultato. L'applicazione di .readOnly()
alla proprietà computata ci assicura che essa non può essere settata in alcun modo.
Bello! E se per qualche ragione la variabile privata viene impostata più volte?
Un ultimo passo
Come suggerisce la domanda precedente, se la _variabilePrivata
è impostata più volte all'interno del servizio, lasciando il codice attuale, potremmo perdere i nuovi valori di quest'ultima.
Le proprietà computate infatti, vengono calcolate quando viene chiamata per la prima volta una get
su di loro. La prima volta che avremo la chiamata istanzaDiEsempioDiService.get('valoreVariabilePrivata')
Ember computerà tale valore e lo restituirà.
La seconda volta che verrà effettuata la stessa chiamata però, se nessun valore delle proprietà nella lista delle dipendenze di tale computed property sarà cambiato, Ember non starà a rivalutare il suo valore ma restituirà quello cachato precedentemente.
Notiamo che nessuna dipendenza è presente nella lista delle dipendenze di valoreVariabilePrivata
ed il problema è che per ora, non abbiamo inserito nel codice un modo per dire ad Ember
Hey, quando il valore di questa proprietà cambia, ricalcolami quanto vale adesso quest'altra proprietà.
Facciamolo:
// La variabile che vogliamo mantenere privata
let _variabilePrivata;
App.EsempioDiService = Ember.Service.extend({
// Proprietà che vogliamo sia pubblica
proprietaPubblica: null,
valoreVariabilePrivata: Ember.computed('contatoreAggiornamento', function() {
return _variabilePrivata;
}).readOnly(),
leggiValoreProprietaPubblica() {
return this.get('proprietaPubblica');
},
impostaVariabilePrivata(valore) {
this.incrementProperty('contatoreAggiornamento');
_variabilePrivata = valore;
},
});
Incrementiamo la property contatoreAggiornamento
chiamando this.incrementProperty('contatoreAggiornamento');
e lo facciamo appositamente ogni qual volta viene modificato il valore di _variabilePrivata
.
Abbiamo aggiunto alla liste delle dipendenze di valoreVariabilePrivata
proprio contatoreAggiornamento
.
L'effetto che otteniamo è il seguente: come al solito, la prima volta che vi è una get
su valoreVariabilePrivata
verrà computata e restituito il valore di _variabilePrivata
.
Adesso ipotizziamo di cambiare _variabilePrivata
chiamando nuovamente impostaVariabilePrivata
, questa volta viene aggiornato anche il valore di contatoreAggiornamento
che sta nella lista delle dipendenze di valoreVariabilePrivata
.
Quando vi sarà una nuova get
su valoreVariabilePrivata
, questa volta Ember noterà che il valore di una delle dipendenze (l'unica in questo caso) è cambiato (contatoreAggiornamento
) e non userà il valore cachato ma andrà a ricomputarlo, restituendo il nuovo valore di _variabilePrivata
!
Conclusioni
Abbiamo dunque ottenuto un modo semplice per la dichiarazione di variabili private associate ad oggetti Ember, rendendole leggibili ma non modificabili dall'esterno. 🤓