viernes, 27 de abril de 2012

El patrón Módulo en JavaScript

El patrón módulo se ha hecho tremendamente popular entre los desarrolladores de JavaScript porque proporciona una buena forma de estructurar y organizar el código que compone un proyecto (múltiples módulos) o una librería (normalmente un único módulo).

El patrón se basa en dos elementos fundamentales:
  • Definir un espacio de nombres
  • Utilizar una función auto-ejecutable para devolver un objeto con la funcionalidad que necesitamos

Definimos el espacio de nombres para que todos nuestros objetos y variables queden siempre dentro de una única variable global ( nuestro namespace ).

Después utilizamos una función autoejecutable para devolver un objeto que contiene todas las propiedades y métodos que necesitamos. Esta función se pueden definir también variables y métodos privados, que no serán accesibles desde fuera y no formarán parte de las propiedades del objeto devuelto.

Un ejemplo básico del patrón módulo sería:

var myModule = (function () {
   var privateVar1 = 1;
   var privateVar2 = "dos";
 
   function privateMethod() {
     // metodo privado
   };
   return {
       publicProperty: " una variable pública ",
       publicMethod: function () {
           //esto es un método público de nuestro módulo,
           //que utilizará internamente el método y 
           //variables privadas definidas
       
        } 
   }
}());

El código anterior muestra el patrón tal y como lo popularizó Douglas Crockford. La función se ejecuta automáticamente y devuelve el objeto en la variable myModule.

Los métodos se podrán acceder desde fuera usando nuestra entrada en el espacio de nombres ( myModule ). De esta forma el módulo dejará visible únicamente una variable global que dará acceso a sus métodos y propiedades.

Cuando definimos un módulo único, el espacio de nombres consiste sólo en declarar una variable que contendrá el objeto devuelto. Cuando tenemos aplicaciones más complejas, compuestas por varios módulos, podemos tener espacios de nombres con varios niveles de profundidad, del tipo:

mailApp.inboxManager
mailApp.notifications
mailApp.cache
....


¿Porqué no asignar el objeto directamente?


Esta es una pregunta muy lógica, si al final va a quedar algo como esto:

var myModule = {
           publicProperty: " una variable pública ",
           publicMethod: function () {
              //esto es un método público de nuestro módulo,
              //que utilizará internamente el método y 
              //variables privadas definidas
       
           } 
}


podríamos crear módulos así desde el principio y definir todas nuestras funciones directamente dentro del objeto que asignamos a myModule, ¿no?.
Sí. Si no queremos/necesitamos tener ninguna variable ni método privado, podríamos hacerlo. De hecho esto es otra forma muy común de definir un módulo.
El patrón Módulo es más potente porque proporciona la posibilidad de definir elementos privados, encapsulados dentro del objeto y no accesibles desde fuera. Sólo se proporciona una API pública concreta, escondiendo todo lo demás.

Modulo revelado. Una versión mejorada del patrón

Christian Heilmann propuso una pequeña variación sobre el patrón módulo tradicional que proporciona algunas ventajas:


var myModule = (function () {
   var variable1 = 1;
   var variable2 = "dos";
   var variable3 = "tres";
 
   function oneMethod() {
     // despues podemos hacer este método público o privado
   }

   function anotherMethod() {
     // despues podemos hacer este método público o privado
   }

   function oneMoreMethod() {
     // despues podemos hacer este método público o privado
   }

   return {
      publicProperty: variable3,
      publicMethod: oneMoreMethod
   }
}());


El patrón Modulo Revelado de Heilmann soluciona los siguientes problemas:

En el patrón original, las propiedades y funciones privadas se declaran de forma diferente de las públicas. Ahora todas las propiedades se declaran igual. Al final, en el return, 'revelamos' sólo los métodos/propiedades que queremos hacer públicas.

Facilita los cambios de visibilidad en el futuro ( pasar una propiedad pública a privada y viceversa).

No tenemos que utilizar nombres largos para acceder desde una función pública a otra. Por ejemplo, si tenemos publicMethod2() que utiliza internamente publicMethod1(), antes tenia que llamarlo con el prefijo del espacio de nombres:


var myModule = (function () {
   ...
   ...
   return {
      publicMethod1: function () { ... },
      publicMethod2: function () {
        ...
        var tmp = myModule.publicMethod1(); 
        ...
      } 
 }
}());

Esto puede ser muy engorroso para proyectos grandes ( el uso de this trata de evitarse en este patrón ). Con el nuevo modelo, desde dentro de una función puede accederse a otra simplemente con su nombre.

¿Y si usamos una función constructora de toda la vida?

Las dos formas de crear módulos que hemos visto a ahora no requieren ser instanciadas utilizando el operador new, pero también podemos simplemente utilizar una función constructora para crear un módulo:


    
var myModule = new function () {
   var privateVar1 = 1;
   var privateVar2 = "dos";
   var privateMethod = function () {
      // metodo privado
   }
 
   this.publicProperty: " una variable pública ";

   this.publicMethod = function () {
       //esto es un método público de nuestro módulo,
       //que utilizará internamente el método y 
       //variables privadas definidas
       
    }; 
 }

Estamos instanciando el constructor directamente con new y guardando el objeto creado en la variable myModule.
Este método nos permite también tener variables y métodos privados y devolver sólo una API pública. El API ofrecido al exterior está formada por todas las propiedades que declaramos como this.property.

Douglas Crockford explica en este artículo que no es una buena idea utilizar new directamente delante de function. No proporcionan ninguna ventaja a la hora de crear objetos.

myObj = new function () {
    this.type = 'core';
};

Es mejor utilizar un objeto literal. Es más corto y más rápido:

myObj = {
    type: 'core'
};

Incluso si necesitamos utilizar variables privadas, es mejor utilizar el patrón módulo. Al utilizar el operador new para invocar a la función, se crea también un prototipo ( otro objeto ) que no nos sirve para nada en este caso. Si no utilizamos new no se creará el prototipo.

La función constructora es más apropiada cuando vamos a necesitar varias instancias del objeto que creamos, con diferentes parámetros de inicialización (para esto utilizamos this) y, sobre todo, si necesitamos la herencia (prototipo). En el caso de un módulo, lo normal es crear un objeto único.


Referencias:

Show love to the module pattern
Revealing module pattern
Module pattern vs anonymous constructor (Stack Overflow)
Design patterns - Module (Addy Osmani)


3 comentarios:

  1. Genial artículo y buenas reflexiones!
    Solo una pequeña corrección respecto a lo que comentas sobre el patrón Módulo Revelado. Desde el return se puede acceder a las propiedades y métodos privados sin necesidad de llamar al modulo, es decir, esta linea:

    var tmp = myModule.publicMethod1();

    quedaría así:

    tmp: publicMethod1()

    Recuerda que dentro del return el signo de igualdad se sustituye por dos puntos ( : ) y no se utiliza el punto y coma para cerrar ; )
    Saludos!!!


    ResponderEliminar
    Respuestas
    1. Ten en cuenta que esa línea no es parte del API que se devuelve en el return. Es una línea que quedaría dentro de publicMethod2 para mostrar el uso de una función dentro de uno de los métodos que se devuelven. Por eso lleva el igual (=) y termina en ';'. Es una sentencia normal.

      He cambiado la indentación para que quede más claro, porque es cierto que resultaba confuso.

      Gracias!

      Eliminar
  2. Excelente explicacion, mucgas gracias por tu tiempo y dedicacion!

    ResponderEliminar