El Patrón Módulo Javascript en Profundidad

11 Abr 2011

Introducción

En nuestra obsesión por crear mejores patrones de diseño en Javascript, hay que reconocer que uno de los más utilizados y elegantes a la hora de organizar código es el que llamamos Módulo (Module Pattern).

Introducido por Douglas Crockford hace ya algunos años, ha sido revisado en varias ocasiones y tomado como punto de partida para grandes arquitecturas. Su principal atractivo es que resulta extremadamente útil para conseguir código reusable y, sobre todo, modular.

Su estructura básica es sencilla: se trata de una función que actúa como contenedor para un contexto de ejecución. Esto quiere decir que en su interior, se declaran una serie de variables y funciones que solo son visibles desde dentro del mismo.

En un entorno de programación orientado a objetos, a esas variables internas las llamamos propiedades mientras que a las funciones, las llamamos métodos.

El esquema de este patrón es el siguiente:

// Namespace for the library
var MyLibrary = {};
 
// Library definition
MyLibrary = (function () {
  // Private variables / properties
  var p1, p2;
 
  // Private methods
  function aPrivateMethod() {
  }
 
  // Public API
  return {
    publicMethod: function () {
    },
 
    anotherPublicMethod () {
    }
  }
}());

La importancia del contexto y de las variables globales

Para mantener el namespace (contexto globlal) lo más limpio y seguro posible, hemos declarado una única variable global que se corresponde con el nombre del módulo. Este es el punto fundamental de este patrón y por ello, nos vamos a detener un momento para analizarlo.

Una aplicación Javascript desarrollada sin un patrón de fondo, termina convirtiéndose en una serie de variables y funciones repartidas a lo largo del código sin demasiado orden. Este escenario es lo que solemos llamar un Spaghetti Code por lo difícil que resulta de seguir y mantener.

Además del orden, un código lleno de variables globales implica un alto riesgo de colisión con librerías bibliotecas de terceros que queramos implementar más tarde. Esto mismo se aplica a nuestro propio programa: si quisieramos reutilizarlo en el futuro para otro desarrollo, tendríamos que tener cuidado a la hora de copiar y pegar, para no sobreescribir en el destino variables o funciones con el mismo nombre.

Con un único nombre global, no corremos el riesgo; de hecho, si se diera el caso de colisión, tan solo tendríamos que cambiarlo sin preocuparnos de todo lo que hay dentro. Y es que es ahí, donde está el valor del módulo: todo lo que ocurre en su interior, pertenece a su propio contexto y no interfiere con lo que puede haber fuera. Es el concepto de encapsulación tradicional manteniendo las distancias.

Los módulos son autoejecutables

El módulo funciona porque, en último término, se trata de una función autoejecutable. Si nos fijamos en el esquema anterior, el nombre del módulo se desarrolla como una función entre paréntesis que acaba con otro par más:

MyLibrary = (function(){
  // Code goes here...
}());

NOTA: Para conocer más sobre este tema, se recomienda el artículo: “Funciones autoejecutables en Javascript“.

Esta estructura permite inicializar todo su interior, o ejecutarlo. La magia está en que, como sabemos, toda función Javascript devuelve algo. Ese algo es aquello que aparezca acompañando al comando return. Inclusive, si una función no se ha programado para que devuelva algo (por ejemplo, no tiene un return), siempre devolverá undefined.

Jugar con el contenido de return es lo que permite que existan métodos públicos en oposición a los privados.

Métodos Públicos y Privados

Ya hemos comentado como las variables declaradas dentro del módulo se conocen como propiedades y son solo válidas dentro de su contexto (clausura). En el caso de las funciones, los métodos, podemos establecer cuáles son privados y cuáles son públicos.

Un método privado es una función a la que solo tienen acceso otros métodos dentro del módulo. Son por lo general funcionalidades internas que no interesan que sean accedidas desde fuera como cálculos o algoritmos.

Por otro lado, un método público es una función que puede ser llamada desde fuera del módulo y que configura la API del mismo. Estas funciones son las que devuelven un determinado valor y son la razón final del programa.

Esta separación entre ambos tipos de método son de sobra conocidos para aquellos programadores que provienen de otros lenguajes orientados a objetos más tradicionales como Java. Sin embargo, en Javascript, al no disponerse del concepto tradicional de clases, resulta algo más complejo de comprender para el principiante.

Si observamos el ejemplo anterior, el esquema, vemos que aquellas funciones declaradas en el cuerpo del módulo son lo que estamos llamando métodos privados. Para conseguir que una de estas funciones sea pública y accesible desde fuera, debemos devolverla a través del comando return. De este modo, al ser el módulo una función autoejecutable (acaba con un par de paréntesis), los métodos que están dentro del return, pasan al contexto global y a estar disponibles en tiempo de ejecución.

Ejemplo básico de uso

Para mostrar su implementación más sencilla, tomemos el siguiente ejemplo:

var myApp = ( function(){
 
  var foo = 'Module Pattern';
  var bar = 'ver 1.0';
 
  var sum = function( param1, param2 ){
    return param1 + param2;
  }
 
  return {
    myMessage: function(){
      return foo + ' ' + bar;
    }
  }
})();
 
console.log( myApp.myMessage() ); // Module Pattern ver 1.0
console.log( myApp.sum( 10, 5 ) ); // myApp.sum is not a function. sum es privada

Para empezar, hemos simplificado su inicialización: declaramos la variable global al tiempo que definimos su contenido.

Si miramos en el interior de nuestro módulo, encontramos con que tenemos dos propiedades: foo y bar. Estas dos variables son únicamente visibles dentro de nuestro módulo, por lo que no existe posibilidad de que colisiones fuera del mismo.

A continuación, hemos declarado un método, sum, que como explicamos en el punto anterior, es privado: esta función solo es accesible desde dentro del módulo quedando por lo tanto protegida.

El siguiente punto es el más importante: nuestro módulo tiene que hacer algo fuera de él y su comportamiento lo establecemos desde el return: es la API de nuestro código. Si lo observamos detenidamente, vemos que se trata en realidad de un objeto, con llaves:

return {
  // ...
}

Como cualquier objeto Javascript, se compone de pares nombre/valor. Será utilizando el nombre como accedamos desde fuera al valor que hayamos definido. En el caso del ejemplo, este valor corresponde con una función anónima que accede a las propiedades privadas que definimos más arriba.

Nota: Sería perfectamente válido devolver por ejemplo, un valor directamente:

return {
  myValue : 'Hello World'
}

La función anónima se encarga de buscar dentro del objeto, las propiedades o métodos que hayamos indicado: en este caso, se toma el valor de foo y bar.

Cuando accedemos desde fuera a nuestro módulo,

console.log( myApp.myMessage() );
console.log( myApp.sum( 10, 5 ) );

vemos como necesitamos utilizar como prefijo su nombre seguido del método público al que queremos ejecutar.

En el ejemplo anterior, sum era un método privado y, por tanto, no accesible desde fuera. De ahí que si tratamos de ejecutar la función, el intérprete nos indique que no está definida. En cambio, myMessage si es un método público al estar contenida en el return: desde ahí, puede acceder a las propiedades privadas y mostralas.

Si quisieramos que el método de suma (sum) fuera público, tan solo tendríamos que incluirlo dentro del return:

return {
    myMessage: function(){
      return foo + ' ' + bar;
    },
    sum : function( number1, number2 ){
     return sum( number1, number2 )
    }
  }

NOTA: Un error de sintaxis común es utilizar un punto y coma (;) para separar métodos. No hay que perder de vista que estamos definiendo un objeto y que, sus pares se separan con una coma simple (,).

En esta ocasión, como nuestro API utiliza parámetros para la suma, la función anónima los recoge y los pasa al método para que éste opere y devuelva un resultado:

console.log( myApp.sum( 10, 5 ) ); // 15

Optimizando el contexto

Para obtener un mejor rendimiento del patrón anterior y evitar errores de sobreescritura, podemos utilizar el último par de paréntesis de nuestro módulo para enviar parámetros seguros:

var myApp = ( function( window, undefined ){
  var foo = 'Module Pattern';
  var bar = 'ver 1.0';
 
  // More code....
 
})( window );

En este caso, hemos enviado como parámetro a nuestro módulo el objeto window para guardar una copia del mismo: esto es interesante, por ejemplo, por razones de rendimiento. Cada vez que necesitemos acceder a este objeto, el intérprete lo hará a través de la copia cacheada en lugar de obligarlo a buscarlo remontando niveles.

Si observamos, además la declaración de la función, hemos especificado un segundo parámetro, undefined, que no se corresponde con ningún argumento (solo hemos especificado window). Esto nos garantiza que undefined será, efectivamente, undefined ya que está sin definir. La idea con esto es evitar los errores que pueden darse en caso de que esta palabra reservada haya sido reescrita en alguna parte del código y su valor no corresponda con el esperado.

Conclusión

El Módulo es uno de los patrones de diseño más utilizados en Javascript. Su simplicidad encierra una gran potencia y flexibilidad: permite mantener el contexto global limpio de variables y funciones. Además, permite reutilizar nuestros códigos de una manera prácticamente transparente: la existencia de métodos privados y públicos permite acercar al entorno Javascript el concepto de clase presente en otros lenguajes y tan demandado por los desarrolladores.

Sin embargo, este módulo presenta algunos problemas como son la necesidad de utilizar un prefijo cada vez que se pretende acceder a un método público o, precisamente, la distinción que existe entre la forma de llamar a unos y otros. Esto implica que, si en un momento dado, quisiéramos cambiar la visibilidad de un método (hacer público uno privado por ejemplo), tendríamos que modificar el objeto general moviendo funciones desde o hacía el objeto return.

En próximos articulos veremos algunas variaciones sobre esta estructura que buscan precisamente corregir esos problemas.

Más:

{14} Comentarios.

  1. Madman

    Poder simular el manejo de clases con sus propiedades y sus métodos en javascript está verdaderamente genial. Tendrá sus inconvenientes pero sus ventajas son muy potentes y descubren un gran campo para explotar javascript.

    A mi personalmente me gusta más el patrón ‘This namespaces proxy’ de James Edwards, del que hablaste ya en web.ontuts.com.

    Gracias por tratar este tema en el blog. Para mi, el uso de patrones fue un gran descubrimiento y los considero como el corazón que da vida a todo desarrollo.

    Saludos!

    • Carlos Benítez

      Los patrones de diseño son, ciertamente, uno de los campos más interesantes de la programación. Todo desarrollador debería conocer tanto los más generales (facade, decorator, singleton) como aquellos más específicos de un lenguaje antes de lanzarse a programar una aplicación.

      Es realmente frustrante tomar el código de otro y comprobar que el código ‘huele mal’ (utilizando la terminología del desarrollo ágil) porque se desconocían los principios más básicos de arquitectura y diseño.

      En próximos artículos trataré otros patrones interesantes que deberíamos tener muy en cuenta a la hora de planear un futuro desarrollo.
      Saludos!

  2. JuniHH

    Tengo varias funciones y efectos desarrollados con javascript y al usarlos con cierta frecuencia en mis proyectos, decidí guardarlos a modo de librería como hice con las clases para mis proyectos en ActionScript.

    Buscando la forma más eficiente me fuí por los prototypes:

    var Clase = function () { };
    Clase.prototype.setLoad = function ()
    {
    ….
    };

    Luego, al declarar un objeto:

    var variable = new Clase ();

    Podía usar el mismo objeto sin recargar la librería nueva vez. Es que en algún lugar leí que al declarar el objeto, las funciones se guardan en él y se puede reutilizar, aumentando la eficiencia contra esa alternativa que muestras.

    El patrón módulo me parece muy elegante, pero me abres nuevamente la duda si será menos eficiente que los prototypes. Decir que la distribución de los prototypes no son nada elegantes, comparados con este método.

    • Carlos Benítez

      En realidad, es que estamos hablando de aproximaciones diferentes.
      Hay que pensar en el Módulo como un componente del programa con una lógica completa: algo asi como un subprograma que podría ser perfectamente funcional por si mismo.

      La forma que tu describes es la estructura más básica de creación de objetos: los constructores. Podríamos decir que, mientras el módulo asume gran parte de la responsabilidad de una aplicación, los constructores estarían diseñados únicamente para unas pocas funcionalidades. En este escenario, yo prefiero utilizar el concepto de herencia prototípica porque me resulta más flexible: en lugar de recurrir al constructor, creo objetos independientes que hereden entre sí los métodos y propiedades que necesito compartir.

      La ventaja del módulo es básicamente ofrecer un contexto único en el que desplegar toda la lógica. Con el sistema clásico de constructores, finalmente tendríamos el namespace lleno de instancias de objetos y, como tu mismo comentas, un código mal distribuido (‘mal’ desde un punto de vista de diseño).

      En cuanto a eficiencia, no hay cambios significativos: el módulo es al fin y al cabo un objeto con sus propios métodos y propiedades e internamente, es tratado igual que las instancias mediante constructores.

      Saludos!

  3. matias

    excelente aporte, hace tiempo que venia leyendo al respecto y vos lo me lo explicaste como a un niño de 5 años.
    simplemente genial 🙂

  4. Victor

    Muy buen articulo y mucho mejor la explicación, siento que abriste mi mente a nuevos conceptos, solo tengo una duda que es la parte final que explica sobre:

    var myApp = ( function( window, undefined ){
    var foo = ‘Module Pattern’;
    var bar = ‘ver 1.0’;

    // More code….

    })( window );

    como dices cacheamos una copia del objeto window, no entendi muy bien para que se pone como ‘undefined’ y porque window va en la parte final.

    Aparte de eso todo muy bien,

    Saludos

  5. Victor

    Sorry hombre, lei tu ultimo post ‘Estudiando el diseño de jQuery paso a paso’, entendi el paso del objeto window, pero no me queda muy claro el de ‘undefined’

    • Carlos Benítez

      Si; el concepto tras la declaración de undefined puede parecer raro, pero la explicación es sencilla: en Javascript, prácticamente todo se puede sobreescribir, incluido la primitiva undefined. Este código es perfectamente válido:

      var undefined = 'si estoy definido';
      

      Esto quiere decir que, si hemos cambiado el valor de esta primitiva, muchas partes del código pueden no funcionar como se espera. Por ejemplo:

      var foo; // Se declara la variable, pero sin valor. Por lo tanto, si preguntamos por ella, debería ser undefined.
      if( foo == undefined ){ console.log('Variable sin definir); }
      

      El código anterior, ahora no funcionaría porque la igualdad, tras cambiar el valor de undefined, realmente significa:

      if( foo == 'si estoy definido'){ /* ... */ }
      

      Y lógicamente no se cumple tal condición.

      Por eso mismo, para garantizar que undefined vale undefined, recogemos en la función un argumento más que los que pasamos. De este modo, cuando tenga que asignar un valor a undefined, ese valor será precisamente el undefined real: nada.

      (function(foo, undefined){
        /* ... */
      })('Hello');
      

      En el ejemplo anterior, foo valdrá ‘Hello’, pero la variable undefined, no recibe argumento y, por lo tanto, es como el caso de antes: se declara pero al no asignarle valor, asume undefined.

      Si en algún momento hubiésemos sobreescrito su valor original, ahora se lo estamos restaurando. De ahí que sea importante conocer ese truco.

      Espero haberme explicado!
      Saludos!

  6. Franz

    Hola Carlos Benitez, hay alguna diferencia en el ejemplo que has puesto en tu post :

    return {
        sum : function( ){
         return sum( );
        }
      }
    

    con esto:

    return {
        sum : sum
    }
    

    por que lo veo mas reducido.

    • Carlos Benítez

      No, no hay ninguna diferencia siempre que sum se haya definido más arriba como una función válida.

      La forma que sugieres es la más moderna aunque, cuando se trata de devolver una única función, en lugar de usar un objeto en el return, devolveríamos directamente esa función:

      //...
      return sum;
      

      Con esto, ahorraríamos algo de memoria.

      Saludos!

  7. Franz

    Gracias por tu respuesta.
    Estaba pensando algo así

    var myApp = ( function(){
     
      var foo = 'Module Pattern';
      var bar = 'ver 1.0';
     
      var saludo = function( ){
        return 'Hola Que tal';
      }
     var bye= function( ){
        return 'adios!';
      }
     
      return {
        bienvenida : saludo,
        adios: bye,
      }
    })();
    

    como no llevan parametros , veo que es más moderno así =).
    Saludos

  8. Ascis

    Esta ultima se puede mejorar devolviendo simplemente ‘this’

    var myApp = ( function()
    {
    var foo = ‘Module Pattern’;
    var bar = ‘ver 1.0’;

    var saludo = function( txt )
    {
    return ‘Hola, ‘ + txt;
    }
    var bye= function( )
    {
    return ‘adios!’;
    }

    this.bienvenida = saludo;
    this.adios = bye;

    return this;
    })();
    alert(myApp.bienvenida(‘como estamos!’)) // salida: Hola, como estamos!

  9. Sacastel

    Buenas, como hago para pasarle un parametro en la creación de la instancia del módulo, algo como esto,
    var modulo = new Modulo(‘parametro’);

    • Carlos Benítez

      En ese caso, si necesitas instanciar un módulo, debes utilizar una estructura de tipo Constructor que recoja, además, esos parámetros que necesitas.

      Sería algo así:

      var MyModule = function ( params ) {
          // Public API
          return {
              getUserParams: function () {
                  console.info( params );
              }
          }
      };  
      
      var newModule = new MyModule( 'Hello World' );
      newModule.getUserParams(); // Hello World
      

      Saludos!

Deja un comentario

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