jueves, 26 de abril de 2012

El Módulo Revelado (Revealing Module) Javascript


Introducción

En el post anterior, analizamos el patrón del Módulo como ejemplo de estructura válida para aplicaciones Javascript reusables y ordenadas. A través de este tipo de patrones de diseño, es fácil acometer un proyecto con ciertas garantías de que el resultado, será elegante y mantenible.
Sin embargo, también comprobamos como el módulo tenía algunas desventajas en cuanto a su organización interna. Básicamente, el principal problema es la división entre métodos públicos y privados: para que una función pasara a estar disponible como parte del API público, tenía que localizarse dentro del comando return:
var myApp = ( function(){
  var privateMethod = function(){
    return 'I am private';
  }
 
  return {
    publicMethod : function(){
      return 'I am public';
    }
  }
})();
Esto puede resultar un inconveniente cuando se trata de modificar la visibilidad de los métodos. Si en nuestro ejemplo, queremos que privateMethod sea público, tendremos que moverlo desde su posición hasta el interior del return. Lo mismo ocurriría al revés: si tras un refactorizado, el métodopublicMethod es sustituido o completado por otra función, para convertirlo en privado, tendremos que sacarlo fuera.

Buscando alternativas al módulo: Revealing Module

Para evitar todo el trabajo que supone trasladar métodos dentro de un módulo, un patrón muy de moda entre los desarrolladores es el que llamamos Módulo Revelación (Revealing Module).
Con este patrón, buscamos un diseño donde todos los métodos se definan, tanto los públicos como privados, dentro del cuerpo del objeto. Luego, aquellos que formen parte del API público, seránreveledos mediante referencia en el bloque return:
var MyLibrary = {};
 
// Library definition
MyLibrary = (function () {
  // Private variables / properties
  var p1, p2;
 
  // Private methods
  function aPrivateMethod() {
  }
 
  // Public Method
  function publicMethod () {
  }
 
  // Another Public Method
  function anotherPublicMethod () {
  }
 
  // Public API
  return {
    publicMethod: publicMethod,
    anotherPublicMethod: anotherPublicMethod
  }
}());
Como vemos, la estructura general es idéntica a la del módulo tradicional con la excepción de que ahora, todos los métodos quedan declarados en el ámbito cerrado del objeto: solo aquellos que se necesita que sean de acceso externo, son referenciados en el interior del return.
Esta estructura permite cambiar la visibilidad de un método con tan solo revelarlo u ocultarlo en el APIganando así flexibilidad con respecto al diseño tradicional.
Sin embargo, aunque podemos hablar de este patrón como una evolución del anterior, comparte todavía un par de inconvenientes a tener en cuenta: por un lado, seguimos necesitando de un prefijo para invocar a los métodos y, por otro, tenemos el problema de las referencias según el tipo de visibilidad de los mismos. Este último punto merece que nos tengamos un momento para analizarlo.

Referencias. El contexto de this

El comando this en Javascript resulta extremadamente útil pero también muy complejo de utilizar si no se conoce correctamente su funcionamiento.
This, en Javascript, refiere siempre al propietario de la función donde ha sido ejecutado o, en su defecto, al objeto de la que es un método.
Para el caso de una función global, foo(), el propietario es la propia página; en un navegador web, será el objeto window:
function foo(){
  console.log( this );
}
 
foo(); // window
Llamar a this desde un método da, como resultado, el mismo objeto:
var foo = {
  bar : function(){
    return this;
  }
}
 
console.log( foo.bar() ); // Object
NOTA: Para un análisis del comando this, se recomienda la lectura del artículo: “The this keyword“.
This, aplicado dentro del módulo, tiene valores diferentes según se trate de un método público o uno privado:
var myApp = ( function(){
  var privateMethod = function(){
    console.log( 'Private: ', this);
    return 'I am private';
  }
 
  return {
    publicMethod : function(){
      console.log( 'Public: ', this);
 
      // Calling the private method:
      privateMethod();
 
      return 'I am public';
    }
  }
})();
 
myApp.publicMethod();
El resultado:
Public: Object{ }
Private: Window
Esto quiere decir que no podemos confiar en esta referencia durante el desarrollo ya que, en caso de necesitar cambiar la visibilidad de los métodos, this cambiará también su valor. Este tipo de error (muy frecuente), es complejo de detectar en objetos grandes, anidados o con gran cantidad de métodos.
La regla de oro en este caso es clara: no debemos nunca utilizar this en el interior de un módulo.
Sin embargo, si necesitamos conservar la referencia original (por ejemplo en estructuras de objetos muy anidadas), una solución de emergencia consiste en cachear el valor de this en el primer nivel del objeto:
var myApp = ( function(){
 
  // Caching this
  var $this = this;
 
  var privateMethod = function(){
    console.log('Private: ', $this);
    return 'I am private';
  }
 
  return {
    publicMethod : function(){
      console.log('Public: ', $this);
      privateMethod();
      return 'I am public';
    }
  }
})();
 
console.log( myApp.publicMethod() );
// Public: Window
// Private: Window
Esto garantiza la integridad del valor desde donde quiera que sea invocado.

Conclusión

El patrón del Módulo Revelado supone para muchos desarrolladores una evolución en cuanto al concepto tradicional. Permite, mediante una estructura más compacta, declarar la visibilidad de sus métodos con únicamente una referencia. Esto evita la separación lógica de bloques que tanto puede dificultar un refactorizado o posterior modificación.
Sin embargo, ambos patrones (el tradicional y el revelado) comparten aún ciertos inconvenientes: uno de ellos continúa siendo la necesidad de llamar a cada método precediéndolo del nombre del objeto, algo que a la larga resulta una desventaja en caso de que varios módulos se encadenen y creando dependencias entre sí.
Por otro lado, tenemos el problema que supone el no poder confiar en las referencias internas que establece this, algo también desventajoso ya que obliga a cachear valores para mantener la integridad en determinados contextos.
En próximas entregas, continuaremos avanzando en este sentido con nuevos patrones de diseño que traten de superar estas dificultades.

No hay comentarios:

Publicar un comentario