En mi anterior post hablé sobre el concepto de clausuras en Java_Script (JS a partir de ahora), y de cómo les podíamos sacar partido en programación avanzada en este lenguaje. En esta ocasión voy a rematar aquello comentando la existencia de auto-clausuras, un concepto muy interesante al que se le puede sacar partido para conseguir algo a priori imposible en este lenguaje: miembros privados en clases JS.
En JS podemos definir una clase de la manera convencional, tratando al mismo tiempo de encapsular el acceso a las variables privadas simulando la existencia de propiedades con funciones 'get' y 'set', por ejemplo así:
function coche() {
var modelo = "NINGUNO";
this.getModelo = function () {
return modelo;
};
this.setModelo = function (sModelo) {
modelo = sModelo.toUpperCase();
};
}
var miCoche = new coche;
miCoche.setModelo("Ferrari");
alert(miCoche.getModelo()); //Muestra FERRARI
En este caso hemos definido una clase coche que tiene un miembro interno llamado modelo. Para intentar encapsular el acceso a este miembro y poder tener control sobre lo que se hace (que no sea una lectura o escritura directas, vamos) hemos definido dos funciones -que son clausuras- que harán las veces de los métodos getter y setter de una propiedad. De este modo para leer el valor del miembro "privado" usamos getModelo, y para escribirlo setModelo, que en este caso además se ocupa de que el nombre del modelo siempre vaya en letras mayúsculas (por poner un ejemplo de regla de negocio simple y no despistarnos del verdadero objetivo del ejemplo).
Así, si el programador usa estos dos métodos para leer y escribir los miembros privados, como en el ejemplo anterior, todo irá bien. Lo que ocurre es que nadie impide al programador ir directamente a la variable supuestamente privada y hacer esto:
miCoche.modelo = "Ferrari";
alert(miCoche.modelo); //Muestra Ferrari en minúsculas
Con lo cual todo nuestro esfuerzo no vale absolutamente de nada.
Es posible hacer esto porque en realidad en las clases de JS no existen los miembros privados, ya que no hay modificadores de ámbito (public o private, por ejemplo) que nos permitan controlarlo.
¿Cómo podemos solucionar esto gracias a las clausuras?
Antes de continuar vamos a considerar un ejemplo de código muy sencillo como el siguiente:
(function (){
var interno = "Valor Interno";
alert(interno);
}
)();
En este ejemplo hemos definido una función que se llama a si misma, y por lo tanto sólo existe durante el instante en que se ejecuta. No queda rastro de ella una vez que se ejecuta. Sí, es raro y aparentemente no tiene utilidad alguna, pero ten un poco de paciencia.
Lo que estamos definiendo en el fragmento anterior es una función anónima y también una auto-clausura. Es una función que se define, se ejecuta, y luego desaparece sin que pueda volver a ser usada. En realidad no desaparece sino que al no haber una referencia a la misma en ningún lugar de nuestro código no tenemos forma de usarla de nuevo ni de acceder a sus miembros. O sea, en cuanto se ejecuta la variable interna deja de ser accesible en nuestro código pues no hay una referencia a la función en ningún lugar.
¿Qué pasaría si de algún modo pudiésemos definir una función como esta que desaparezca pero quedarnos con una refencia a algo que esté en su interior (una clausura interna de esta función efímera)? Pues pasaría que, de repente, tendríamos acceso a los miembros de esta función efímera pero sólo de manera indirecta, nunca directa como en el caso anterior ya que no tenemos ninuna variable que apunte a la propia función.
Este es el concepto que usaron por primera vez los chicos de Yahoo y que ha permitido definir clases con miembros privados en JS.
Vamos a verlo con un ejemplo práctico reproduciendo la clase anterior que representa a un coche, pero esta vez que sólo sea accesible a través de los métodos getter y setter que hayamos definido:
var coche = function() {
var modelo= "NINGUNO";
return {
getModelo : function () {
return modelo;
},
setModelo : function (sModelo) {
modelo = sModelo.toUpperCase();
}
};
}();
alert(coche.modelo); //Undefined
alert(coche.getModelo()); //NINGUNO
coche.setModelo("Smart Fortwo");
alert(coche.getModelo()); //SMART FORTWO
Fíjate bien en este código como se define una función que se ejecuta inmediatamente poniéndole un (); al final (línea 11), y almacena el resultado de su ejecución en una variable llamada coche. El código interno de esta función define una variable modelo que al igual que antes contendrá el nombre del modelo de coche que estamos manejando. Además como resultado de ejecutar la función se devuelve un nuevo objeto interno que tiene dos métodos que leen o fijan el valor de la variable interna. Como están dentro de la función son dos clausuras que tienen acceso a sus miembros (en este caso la variable modelo), pero como n queda referencia a la función, pues la estamos ejecutanod inmediatamente, entonces la única forma de acceso a la variable modelo que nos queda es a través de esas clausuras. ¡¡Lo que tenemos en la práctica es una variable privada!!. Algo imposible de conseguir a priori. Los alert del final del código anterior nos demuestran que así es. El primero de ellos intenta acceder a la variable modelo pero no puede, porque realmente tiene una refefencia a un objeto interno de la función, no a ésta, así que no puede y devuelve un valor "undefined" ya que no existe dicho miembro para el objeto coche. En las otras tres líneas usamos los métodos "legales" para acceder a la variable y todo funciona correctamente.
Instancias únicas de clase: Singleton
En la práctica ademas obtenemos una clase de instancia única (Singleton), ya que sólo existe una referencia a la misma y no es posible crear nuevas instancias. Esto es muy útil cuando estamos definiendo marcos de trabajo como el mencionado antes de Yahoo, y esta técnica ha sido usada desde entonces por otros frameworks como los mencionados en el post anterior.
Sin embargo nos impide crear clases con miembros privados y que podamos utilizar varias veces. Es decir, tal y como está el código anterior sólo podemos tener un único coche, no varios.
Si quitamos el doble paréntesis de antes (de la línea 11) que fuerza la ejecución, podemos trabajar con ella como si de una clase normal se tratara y seguimos teniendo acceso limitado a los miembros privados:
var Coche = function() {
var modelo= "Ninguno";
return {
getModelo : function () {
return modelo;
},
setModelo : function (sModelo) {
modelo = sModelo.toUpperCase();
}
};
};
var coche1= new Coche();
var coche2= new Coche();
alert(coche1.modelo); //Undefined
coche1.setModelo("Porsche");
coche2.setModelo("Smart");
alert(coche1.getModelo()); //PORSCHE
alert(coche2.getModelo()); //SMART
Ahora creamos dos objetos de la clase Coche y les asignamos valores a través de su método setter. Si te fijas en la línea 15, intentamos acceder a su variable privada modelo sin éxito, pues se devuelve un indefinido, así que estamos consiguiendo en la práctica clases con miembros privados y que además podemos instanciar tantas veces como queramos.
Una vuelta de tuerca más al lenguaje de script más popular que tiene una gran utilidad práctica y que espero que te resulte útil :-)