El Módulo Revelado
(Revealing Module) Javascript

12 Abr 2011

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étodo publicMethod 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án reveledos 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 API ganando 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.

Más:

{8} Comentarios.

  1. ender wiggins

    joer, lo que gustan las declaraciones inline…

    ¿no os gusta el sistema declarativo clásico?

    MiClase.prototype._internalValue = 0;
    MiClase.prototype.ExternalValue = 1;

    //constructor
    function MiClase() {
    }

    MiClase.prototype.getElements = function (typeElements) {
    //…
    }

    • Carlos Benítez

      El sistema declarativo clásico es el primer paso para limpiar el namespace general y no usar variables globales. Sin embargo, tiene desventajas en cuanto a orden y legibilidad.

      Utilizando la notación literal, podemos evitar hacer referencia al objeto global cada vez que necesitamos crear nuevos métodos. Además, conseguimos un código más limpio y ordenado.

      Existen también importantes consideraciones con respecto al uso de ‘this’ en el sistema declarativo que evitamos con los patrones literales.

      Tienes un análisis más en detalle sobre estos temas aquí:
      http://web.ontuts.com/tutoriales/namespacing-en-javascript/

      De todos modos, en programación, al final se trata de sentirnos cómodos con un esquema.
      Saludos!

  2. guzman

    a mi me gusta mas como se gestiona la privacidad de los metodos como recomienda jQuery en la autoria de plugins
    http://docs.jquery.com/Plugins/Authoring

    (function( $ ){
    
      var methods = {
        init : function( options ) { // THIS },
        show : function( ) { // IS   },
        hide : function( ) { // GOOD },
        update : function( content ) { // !!! }
      };
    
      $.fn.tooltip = function( method ) {
        
        // Method calling logic
        if ( methods[method] ) {
          return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
          return methods.init.apply( this, arguments );
        } else {
          $.error( 'Method ' +  method + ' does not exist on jQuery.tooltip' );
        }    
      
      };
    
    })( jQuery );
    

    expones los metodos que te interesan (los que estan en methods )

    • Carlos Benítez

      Buen aporte Guzmán;
      le dedicaré un post al sistema jQuery dentro de poco.

      Saludos.

  3. Emiliano

    Muy interesante la información que vengo leyendo aquí hace tiempo. Estaría bueno saber de qué fuente la obtenés así es posible consultarla para más información.

    Muy buen sitio, saludos!

    • Carlos Benítez

      Básicamente se trata de apuntes que vamos obteniendo con la práctica y leyendo mucho código fuente.

      Existen algunos libros que hablan sobre patrones de diseño en Javascript; son pocos, pero excelentes:

      Javascript: The Good Parts, de Douglas Crockford.
      JavaScript Patterns, de Stoyan Stefanov.
      Essential JavaScript & jQuery Design Patterns de Addy Osmani (gratuito).

      Además de esos tres, en la bibliografía suele encontrarse también algo sobre el tema pero, como he dicho antes, lo mejor es leer mucho código para extraer de ahí las prácticas que nos resulten más interesantes.

      Saludos!

  4. Madman

    En mi opinión, este patrón con tan solo un cambio hace que el sistema de declaración de métodos sea 10 veces más manejable y cómodo de mantener.

    En cuanto al uso de la referencia ‘this’, como bien dices, se puede cachear al iniciar nuestro objeto y así tenerlo disponible para todos los métodos; pero dices que es una solución de emergencia, ¿se considera poco elegante? La verdad es que el uso continuo de esta referencia es cansina y es cierto que puede llegar a dar problemas e incongruencias. Por el momento pecaré de ignorante por no saber de qué manera se puede mejorar este punto XD

    Un Saludo!!

    • Carlos Benítez

      El cachear ‘this’ no resulta poco elegante pero si arriesgado: cuando trabajamos con métodos muy anidados en objetos igualmente profundos, la referencia es confusa.

      El caché solo devolvería el propietarion original que, estando en el primer nivel, se corresponde con Window. En definitiva, estamos alterando el valor real que debería tener por una mera copia.

      Para estos casos, cada nivel jerárquico debería contar con su propio valor cacheado. Y esa recurrencia si resulta poco elegante. El escenario perfecto sería aquel donde this funcionara como se espera. Y algunos patrones de diseño apuntan en esa dirección 🙂

      Saludos!

Deja un comentario

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