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


2 comentarios:

  1. Hola, que tal. Me parece muy buena la explicación.
    Solo una duda, hay algún caso en especial donde debamos de usar un SINGLETON?
    Es decir, cuándo sí y cuándo no.
    Gracias por el post!

    ResponderEliminar
    Respuestas
    1. La idea del patrón singleton, es retornar siempre la misma instancia de la clase, y almacenar la instancia dentro de un campo estático en la clase, para darle uso internamente entre sus methodos, o poder utilizarlo en un contexto global. Al retornarte siempre la misma instancia también estamos ahorramos memoria.

      Eliminar