sábado, 23 de junio de 2012

El patrón Singleton en Javascript

El patrón singleton se utiliza cuando queremos que exista una sola instancia de una clase particular en toda la aplicación. Cuando alguien instancia una clase singleton debemos comprobar si ya existe una instancia y, si es así, devolver esa instancia. Si no existe, se crea.

 Esta definición encaja muy bien en los lenguajes con una orientación a objetos clásica, como Java, pero JavaScript no tiene clases, su orientación a objetos se basa en prototipos. Esta particularidad, junto con el uso de funciones constructoras para crear algo parecido a una clase, hace que este patrón pueda implementarse de muchas maneras diferentes.

El singleton como un objeto literal

Puesto que el objetivo es que solamente exista un objeto concreto de un tipo ¿ porqué no crearlo directamente ?. En JavaScript podemos crear objetos sin necesidad de tener una clase ni un constructor. Esta es la forma más sencilla de crear un singleton:

var mySingleton = {
 property1: 1,
 property2: 2,
 method1: function() {}
};

Si necesitamos tener variables y métodos privados, podemos utilizar el patrón módulo para crear el objeto:
var mySingleton =  (function() {
 var privateProperty1 = "one";
 var privateMethod1 = function () {};
 return {
    publicProperty1: 1,
    publicProperty2: 2,
    publicMethod1: function() {
             //use private props and methods here
    }
 };

})();

Un singleton más clásico

También podemos implementar el patrón de una forma más parecida al patrón clásico, de forma que tengamos una 'clase' ( realmente una función constructora ) que no se pueda instanciar y con un método público 'getInstance' que nos devuelva el objeto si ya existe o lo cree si no está creado:

var mySingleton = (function() {

    //Singleton constructor is private
    function Singleton() {
        var privateVar1 = "I'm a private var";
        this.publicVar1 = "I'm a public var";
        this.publicMethod1 = function() {
            console.log("Private var: " + privateVar1 + " public var: " + this.publicVar1);
        }
    }

    //private var to store the single instance
    var singleInstance;

    //Return the object providing getInstance method
    return {
        getInstance: function() {
            if (!singleInstance) singleInstance = new Singleton();
            return singleInstance;

        }
    }

})()

En este caso, lo que tenemos en 'mySingleton' es un objeto que proporciona el método público 'getInstance'. Para conseguir la instancia hacemos:

var myInstance = mySingleton.getInstance();

 

Utilizando el operador 'new'

Podemos hacerlo aún más sencillo de usar, de forma que que el usuario no tenga que saber que está instanciando un singleton ( llamando a getInstance ). Puede hacer 'new' como con cualquier otra clase y obtener la instancia única.

Esta sería nuestra clase ( nuestra función constructora ):

function MySingleton () {
   if ( MySingleton.singleInstance ) return MySingleton.singleInstance;  
   MySingleton.singleInstance = this;
   this.publicProperty1 = 1;
   this.publicProperty2 = 2;
   this.publicMethod1: function() {};
} 

Podemos instanciar la clase con 'new' todas las veces que queramos, obtendremos siempre la misma instancia:

var mySingletonInstance1 = new MySingleton();
var mySingletonInstance2 = new MySingleton();
var mySingletonInstance3 = new MySingleton();
// mySingletonInstance1 === mySingletonInstance2 === mySingletonInstance3

Podemos mejorar aún más el código evitando usar una propiedad pública (MySingleton.singleInstance) para guardar nuestra instancia. Es mejor utilizar una variable privada de la siguiente forma:

var MySingleton = (function() {
   var singleInstance;
   return function() {
        if ( singleInstance ) return singleInstance;  
        singleInstance = this;
        this.publicProperty1 = 1;
        this.publicProperty2 = 2;
        this.publicMethod1: function() {};
    }   
})(); 

El uso sería igual que en el ejemplo anterior:

var mySingletonInstance1 = new MySingleton();
var mySingletonInstance2 = new MySingleton();
var mySingletonInstance3 = new MySingleton();
// mySingletonInstance1 === mySingletonInstance2 === mySingletonInstance3


domingo, 17 de junio de 2012

Ya no se diseña para una resolución concreta

Hace ya bastantes años, y durante mucho tiempo, la norma número uno del diseño de páginas web era:

"programa siempre para una resolución de 800x600 px"

Después la resolución 'objetivo' pasó a ser 1024px, que se traducía en diseños de 960px debido al ancho del borde del navegador y la barra de scroll y a que 960 es divisible entre 3, 4, 5, 6, 8, 10, etc, lo que hace fácil una maquetación en columnas.

Todo esto ha quedado obsoleto. Ya no tiene ningún sentido pensar ¿para qué resolución vamos a hacer esta página/aplicación?.

Ya no hay una resolución ni un dispositivo base para hacer un diseño

Cualquier usuario puede acceder a nuestra web con varios dispositivos diferentes, con tamaños muy distintos, y quiere reconocer la web cuando entra. No podemos crear una web completamente diferente para cuando entre con su móvil, otra para cuando entre desde un tablet, otra para PC, etc. El usuario quiere encontrar siempre la web que conoce.

La cantidad de dispositivos y tamaños con los que se puede acceder a nuestro contenido es incontrolable y crece cada pocos meses.

Debemos simplemente programar y diseñar de forma que el contenido se adapte al entorno del usuario y olvidarnos del control. Ya no podemos controlar nuestro diseño como si fuera para una revista. Es imposible controlar las medidas y ajustar el contenido 'al pixel' y esperar que se vea igual en todos los dispositivos posibles. El contenido se debe adaptar al dispositivo.

Si seguimos pensando en tamaños fijos y en una resolución objetivo, estamos viviendo en el pasado. El diseño adaptable (responsive design) no es una forma de hacer las cosas, es ya la única forma aceptable de trabajar para la web.

domingo, 3 de junio de 2012

Espacios de nombres en JavaScript (namespaces)

El manejo de espacios de nombres en JavaScript ha sido ignorado durante muchos años porque los programas eran relativamente sencillos y, normalmente, una página web no se componía de diferentes piezas de código desarrolladas por programadores o empresas diferentes. El código se incluía como un script en la página y la mayoría de las variables y funciones eran globales.

Actualmente, en JavaScript se programan aplicaciones muy complejas ( enormes a veces ) y se han desarrollado cientos de librerías que podemos importar en nuestro código. Esto hace necesario organizar de alguna manera nuestro código para asegurarnos de que nuestras variables, objetos y funciones sean únicas y no se estén sobreescribiendo ( o siendo sobreescritas ) por una variable del mismo nombre de alguna de las librerías externas que utilicemos. Nombres como getElement, counter, checkValue, showMessage, etc son muy comunes y podrían existir en casi cualquier script que importemos.

Para poder utilizar cualquier nombre con seguridad, cada programa debe definir sus variables dentro de un contenedor único. Este contenedor es el espacio de nombres (namespace).

Cada librería externa que utilicemos tendrá su propio espacio de nombres y nosotros debemos definir también el nuestro para nuestro proyecto.

¿Cómo se crea un espacio de nombres?


En JavaScript se utiliza un objeto como espacio de nombres porque no existe una sintaxis nativa para esta funcionalidad.

Por ejemplo, podemos definir el siguiente objeto:

var myProject = {};

y luego definimos nuestras variables y funciones como propiedades de este objeto:

myProject.var1 = "una variable";
myProject.var2 = "otra variable";
myProject.var3 = { uno: 1,
                   dos: 2 };
myProject.doSomething = function () { ... };

De esta forma la única variable global que definimos es myProject y todos los nombres que definimos en el proyecto estarán dentro de este contexto definido por nuestro espacio de nombres. Siempre accederemos a cualquier elemento con el prefijo myProject delante.

La forma de definir las variables dentro del objeto no importa, podemos hacerlo directamente al crearlo:

var myProject = {
    var1:  "una variable",
    var2 = "otra variable",
    var3 = { uno: 1,
             dos: 2 };
    doSomething = function () { ... }

}

o utilizar el patrón módulo para tener también propiedades privadas.

Comprobar si el espacio de nombres está creado


Antes de crear el espacio de nombres es necesario comprobar si ya está creado. Si el namespace ya está definido utilizaremos el objeto ya creado. Esto es especialmente importante cuando nuestra aplicación tiene varios módulos y utilizamos espacios de nombres anidados:

myProject.userInterface
myProject.ajaxHandler
myProject.eventManager

Normalmente cada módulo está en un fichero separado y no podemos hacer:

var myProject = {};
myProject.usrInterface = { 
    //module logic here
}

Porque si myProject ya existe estaríamos borrando todo lo que contenga (todos los otros modulos).

Una forma común de hacer la comprobación es:

var myProject = myProject || {};

o también:

myProject || ( myProject = {} )

En esta última expresión, la segunda parte sólo se ejecuta si myProject es false.

Una función para crear el espacio de nombres


Cuando el proyecto es muy grande puede haber varios niveles de anidamiento en el namespace:

myProject.ui.list
myProject.ui.userForm
myProject.utils.validateInput
myProject.core.ajaxHandler
myProject.core.eventManager
...

Cada módulo tendrá que comprobar, uno a uno, si existen los namespaces de nivel superior antes de crear el suyo. Es mucho más cómodo utilizar una función para crear un espacio de nombres concreto, que se le pasa como parámetro. Todas las comprobaciones quedan encapsuladas en la función.

function createNameSpace( nameSpaceString ) {
    var names = nameSpaceString.split(".");

    //global object is the parent for first level nameSpace
    var parent = window;  
    
    //if any nameSpace level doesn't exist, create it
    for ( var i=0, imax=names.length ; i < imax; i++ ) {
        if ( !parent[names[i]] ) parent[names[i]]={};
        parent = parent[names[i]];
    }      
}

Al principio de cada módulo crearíamos el espacio de nombres de esta forma:
createNameSpace( "myProject.core.ajaxHandler" );