Il controllo d’accesso ai membri di una classe, ovvero la possibilità di definire i membri di una classe private, protected o public è una delle basi su cui si fonda la programmazione ad oggetti, dato che permette di nascondere e incapsulare i dettagli di implementazione e di disciplinare l’accesso dall’esterno alle risorse garantendo che la loro coerenza sia mantenuta. Sfortunatamente Javascript non possiede un sistema integrato di controllo di accesso: in questo articolo descriverò un paio di tecniche che si possono applicare per realizzare forme di accesso controllato ai membri di una classe.
Un primo modo di disciplinare l’accesso a una variabile o a un metodo di una classe è utilizzare un namespacing appropriato. Per esempio è una convenzione piuttosto diffusa usare un underscore come prefisso per i membri di una classe che rappresentano gli internals di un componente software. Questo ovviamente non limita realmente l’accesso alle risorse, ma aiuta un programmatore attento a non compromettere involontariamente lo stato di un’applicazione.
La parte più interessante dal punto di vista tecnico tuttavia è capire come possiamo ottenere dei veri e propri membri ad accesso privato sfruttando un paio di caratteristiche peculiari del linguaggio:
- L’ambito lessicale e non dinamico delle funzioni (l’ambito di esecuzione delle funzioni è quello dove esse vengono definite indipedentemente da dove sono effettivamente eseguite).
- La possibilità di utilizzare le funzioni come un qualsiasi valore, immagazzinandole in variabili o usandole come valori di ritorno di una funzione stessa.
Proviamo a chiarire con un esempio:
function getFx () {
var a = "Hello!";
return function () { alert ( a ); }
}
var fx = getFx();
fx();
Qui definiamo ‘getFx()’, che è una funzione che ritorna una funzione. Immagazziniamo il risultato di ‘getFx()’ nella variabile ‘fx’. L’ambito statico delle funzioni ci garantisce che la funzione immagazzinata in ‘fx’ abbia accesso alle variabili disponibili al momento della sua definizione e non dell’esecuzione. Alla chiamata di ‘fx()’ otterremo come risultato ‘Hello!’, nonostante la funzione ‘getFx()’ sia già ritornata (chi di voi è abitutato ad un ambito di esecuzione dinamico delle funzioni poteva aspettarsi che il contenuto di ‘a’ fosse già stata cestinato…).
Notate niente di interessante? Abbiamo realizzato una forma di incapsulamento avendo una variabile ‘a’ il cui contenuto non è direttamente accessibile, se non tramite la funzione immagazzinata in ‘fx’. L’insieme della definizione di funzione e della catena di ambiti (scope chain) in atto al momento della definizione prende il nome di closure (per scope chain si intende l’insieme degli ambiti visibili dalla funzione stessa, ovvero il proprio ambito, quello della funzione superiore e così via fino all’ambito globale).
Come avrete intutito, il concetto di closure può essere applicato con successo al nostro problema, vediamo con un esempio come:
var Counter = function () {
// Private variables
var self = this;
var value = 0;
// Private methods
var setValue = function ( val ) {
value = val;
};
// Privileged methods
this.get = function () {
alert( value );
}
this.reset = function () {
value = 0;
}
// constructor
setInterval(
function () { value++; },
1000
);
}
Qui la variabile ‘value’ non è modificabile se non tramite quei metodi che sono stati esposti all’esterno definendoli con ‘this.x = function () {}’. I metodi e le variabili definiti con ‘var x = function () {}’ cessano di essere direttamente accessibili una volta che il costruttore ritorna.
Un altra cosa da notare in questo esempio è che il sistema della closure è stato utilizzato anche nella chiamata a ‘setInterval()’. Normalmente questa funzione accetta come primo argomento una stringa che contiene il codice da eseguire periodicamente nell’ambito globale; in questo caso, passando una funzione anonima definita al volo, abbiamo la possibilità di agire sulla variabile ‘value’ anche dopo che il costruttore è ritornato, perchè ‘value’ è contenuto nella closure della funzione.
Quando utilizzare la tecnica basata sul namespacing e quando le closures? Per quanto usare una convenzione nei nomi possa apparire ingenuo, questo metodo ha il pregio di non precludere l’estensibilità dell’oggetto (con le closures i metodi privati non possono essere recuperati nelle sottoclassi) e in generale può utilizzare meno memoria dato che i metodi pseudo privati possono essere inclusi nel prototipo dell’oggetto.
Se avete trovato interessante questo articolo, probabilmente troverete molto utile la lettura di questo libro, continuate a tenere d’occhio questo blog per nuovi articoli su questi temi.