Visibilidades en JavaScript

Una de las cosas que se argumentan en contra de JavaScript cuando se habla de orientación a objetos es que no soporta la visibilidad de métodos o propiedades. Es decir, todo es público por defecto.

Mucha gente hoy en día cuando programa en JavaScript adopta alguna convención tipo “lo que empiece por guión bajo es privado y no debe ser invocado”. Como chapuza para ir tirando, pues bueno, pero en JavaScript hay maneras de simular una visibilidad privada y de que realmente el creador de un objeto no pueda invocar algunos métodos. En este post veremos un método rápido y sencillo. Por supuesto no es el único ni tiene porque ser el mejor…

Empecemos por la declaración de una función constructora que me permite crear objetos de tipo Foo:

  1. var Foo = function () {
  2.     this.count = 0;
  3.     this.inc = function() {
  4.         this._addToCount(1);
  5.     };
  6.  
  7.     this._addToCount = function (a) {
  8.         this.count += a;
  9.     };
  10. }
  11.  
  12. var foo = new Foo();
  13. console.log(foo.count);
  14. foo.inc();
  15. // Esto debera ser privado
  16. foo._addToCount(100);
  17. console.log(foo.count);
  18. // count no debera poder accederse
  19. foo.count = 10;
  20. console.log(foo.count);

Estoy usando la convención de que los métodos privados empiezan por un guión bajo. Pero es esto: una convención. Para el lenguaje no hay diferencia. De hecho si ejecuto este código el resultado es el siguiente:

image

El desarrollador que crea un objeto Foo puede acceder tanto a inc, como a addToCount como a count. Como podemos solucionar eso?

La solución pasa por no devolver a quien crea el objeto Foo entero si no un “subobjeto” que tan solo contenga las funciones publicas:

  1. var Foo = function () {
  2.     this.count = 0;
  3.     this.inc = function() {
  4.         this._addToCount(1);
  5.     };
  6.  
  7.     this._addToCount = function (a) {
  8.         this.count += a;
  9.     };
  10.  
  11.     return {
  12.         inc : this.inc
  13.     };
  14. }
  15.  
  16. var foo = new Foo();
  17. console.log(foo);

Si ejecuto este código parece que vamos por el buen camino:

image

Ahora el objeto foo contiene tan solo el método inc. Pero, que ocurre si ¿lo ejecutamos? Pues eso:

image

JavaScript se queja que el método _addToCount no está definido! Que es lo que ha ocurrido? Lo ocurrido tiene que ver con el contexto de JavaScript o el valor de this. El método inc que invocamos es el método inc del objeto anónimo que devolvemos al final de la función constructora de Foo. Dentro de este método el valor de this es el valor del objeto anónimo que, por supuesto, no tiene definido _addToCount. Parece que estamos en un callejón sin salida, verdad?

Aquí es cuando entra en escena la función bind: bind es un función que se llama sobre una función. El resultado de aplicar bind a una función es otra función pero atada permanentemente al contexto que se pasa como parámetro a bind. Dicho de otra manera cuando devolvemos el objeto anónimo, tenemos que modificar el contexto del método inc para que sea el objeto Foo entero. Así modificamos el return para que quede como:

  1. return {
  2.     inc: this.inc.bind(this)
  3. };

Cuando se ejecuta este return el valor the this es el objeto Foo entero así que lo que estamos devolviendo es un objeto anónimo, con una función inc (que realmente es this.inc es decir la función inc del objeto Foo entero), pero que está bindeada a this (el objeto Foo entero), es decir que cuando se ejecute este método inc del objeto anónimo el valor de this no será el objeto anónimo si no el propio objeto Foo.

Con esto hemos terminado! Ahora cuando llamamos a new Foo(), lo que obtenemos es un objeto solo con el método inc. Cuando invocamos inc todo funciona ahora correctamente. Y ya no podemos invocar el método privado _addToCount ni acceder a la propiedad count.

Esto es tan solo un mecanismo, hay varias maneras distintas de hacer lo mismo pero todas se basan en este mismo principio.

Saludos!

PD: Os dejo el código de un método, que he llamado _publicInterface. Dicho método lo que hace es, a partir de un objeto, crear otro objeto que contenga tan solo aquellas funciones que NO empiezan por guión bajo:

  1. this._publicInterface = function() {
  2.     var keys = Object.keys(this);
  3.     var protocol = {};
  4.     for (var idx = 0; idx < keys.length; idx++) {
  5.         var key = keys[idx];
  6.         if (key.charAt(0) !== ‘_’ && typeof (this[key]) === «function») {
  7.             protocol[key] = this[key].bind(this);
  8.         }
  9.     }
  10.  
  11.     return protocol;
  12. };

Así podéis definir en vuestros objetos funciones públicas y privadas (que empiecen por guión bajo) y en el return final hacer: return this._publicInterface();

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *