domingo, 15 de noviembre de 2015

El patrón de diseño Singleton en Java

El patrón de diseño Singleton restringe la creación de instancias a una clase y se asegura de que sólo existe una instancia de la clase en la máquina virtual de Java, es decir, sólo podemos tener un objeto de la clase por aplicación.



En este artículo analizaré ciertas formas de llevarlo a cabo y los problemas que derivan de cada implementación.







Implementación común.
Es la forma más sencilla de utilizar Singleton, la instancia a la clase es creada mientras se carga aunque la clase cliente no la utilice en ese momento. A esta forma de crear un singleton se le llama “eager initialization”:

public class MiClaseSingleton {
   
    private static final MiClaseSingleton instance = new MiClaseSingleton();
     
    private MiClaseSingleton(){}

    public static MiClaseSingleton getInstance(){
        return instance;
    }
}

Ten en cuenta que intance es inicializado inmediatamente para contener el objeto, el cual no puede tener cambios al ser final. Por otro lado, nadie puede crear una instancia de nuestra clase ya que tiene un constructor marcado como privado.

Si la clase no utiliza muchos recursos esta sería una implementación correcta, pero muchas veces usamos el patrón para conexiones a bases de datos o con recursos del sistema de archivos, por lo que deberíamos evitar crear la instancia hasta que el cliente llame al método getInstance. Por otro lado tampoco manejamos excepciones.



Inicialización como Bloque Estático.
Similar al anterior pero con la opción de manejo de excepciones.

public class BloqueEstaticoSingleton {

    private static BloqueEstaticoSingleton instance;
   
    private BloqueEstaticoSingleton(){}
     
    static{
        try{
            instance = new BloqueEstaticoSingleton();
        }catch(Exception e){
            throw new RuntimeException("Excepción al crear la instancia...");
        }
    }
   
    public static BloqueEstaticoSingleton getInstance(){
        return instance;
    }
}

Al igual que la forma anterior crea la instancia antes de ser usada.

Inicialización bajo demanda (Lazy Initialization) 
En este caso no se crea la instancia al cargar la clase sino cuando es llamado el método getInstance()

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;
   
    private LazyInitializedSingleton(){}
   
    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Esta implementación funciona correctamente mientras nuestra aplicación no sea multihilo, ya que en el caso de tener múltiples threads, cada uno tendrá diferentes instancias de la clase. (Ten en cuenta que instance no está marcada como final)

Implementación Thread Safe. 
La forma más sencilla es hacer nuestro método de getInstance Synchronized para que sólo un hilo lo ejecute a la vez:

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;
   
    private ThreadSafeSingleton(){}
   
    public static synchronized ThreadSafeSingleton getInstance(){
        if(instance == null){
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }
   
}

El precio a pagar es la reducción de rendimiento por el coste asociado al método syncrhonized. Para evitar este impacto en el rendimiento se utiliza un doble condicional:

public static ThreadSafeSingleton getInstanceUsingDoubleLocking(){
    if(instance == null){
        synchronized (ThreadSafeSingleton.class) {
            if(instance == null){
                instance = new ThreadSafeSingleton();
            }
        }
    }
    return instance;
}

El bloque Synchronized dentro de la condición para estar seguros de que una sola instancia es creada.

Implementación Enum
Otra forma muy interesante y tal vez menos usada es como un tipo enumerado:

public enum Singleton {
INSTANCE;
public static void metodo(){
        //hacer algo
    }
}

Las ventajas es que garantizamos que es instanciado una única vez en nuestro programa, es accesible globalmente, es bajo demanada o "lazy" y sin problemas en multihilo.



No hay comentarios:

Publicar un comentario