jueves, 26 de abril de 2012

El Patrón Módulo Javascript en Profundidad


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 llamamospropiedades 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 conlibrerí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 propiedadesy 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 propiedadesfoo ybar. 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 nombrecomo 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, myMessagesi 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.

No hay comentarios:

Publicar un comentario